1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-09 08:45:55 +02:00
joplin/packages/pdf-viewer/PdfDocument.ts
2022-11-15 11:39:51 +00:00

164 lines
4.6 KiB
TypeScript

import * as pdfjsLib from 'pdfjs-dist';
import { ScaledSize, RenderRequest, RenderResult } from './types';
import { Mutex, MutexInterface, withTimeout } from 'async-mutex';
export default class PdfDocument {
public url: string | Uint8Array;
private doc: any = null;
public pageCount: number = null;
private pages: any = {};
private rendererMutex: MutexInterface = null;
private pageSize: {
height: number;
width: number;
} = null;
private document: HTMLDocument = null;
public constructor(document: HTMLDocument) {
this.document = document;
this.rendererMutex = withTimeout(new Mutex(), 40 * 1000);
}
public loadDoc = async (url: string | Uint8Array) => {
this.url = url;
const loadingTask = pdfjsLib.getDocument(url);
try {
const pdfDocument: any = await loadingTask.promise;
this.doc = pdfDocument;
this.pageCount = pdfDocument.numPages;
} catch (error) {
error.message = `Could not load document: ${error.message}`;
throw error;
}
};
public getPage = async (pageNo: number) => {
if (!this.doc) {
throw new Error('Document not loaded');
}
if (!this.pages[pageNo]) {
this.pages[pageNo] = await this.doc.getPage(pageNo);
}
return this.pages[pageNo];
};
public getPageSize = async () => {
if (!this.pageSize) {
const page = await this.getPage(1);
const viewport = page.getViewport({ scale: 1.0 });
this.pageSize = {
height: viewport.height,
width: viewport.width,
};
}
return this.pageSize;
};
public getScaledSize = async (height: number = null, width: number = null): Promise<ScaledSize> => {
const actualSize = await this.getPageSize();
let scale = 1.0;
if (height && width) {
scale = Math.min(height / actualSize.height, width / actualSize.width);
} else if (height) {
scale = height / actualSize.height;
} else if (width) {
scale = width / actualSize.width;
}
return {
height: actualSize.height * scale,
width: actualSize.width * scale,
scale,
};
};
public getActivePageNo = (scaledSize: ScaledSize, pageGap: number, scrollTop: number): number => {
const pageHeight = scaledSize.height + pageGap;
const pageNo = Math.floor(scrollTop / pageHeight) + 1;
return Math.min(pageNo, this.pageCount);
};
private renderPageImpl = async ({ pageNo, scaledSize, getTextLayer, isCancelled }: RenderRequest): Promise<RenderResult> => {
const checkCancelled = () => {
if (isCancelled()) {
throw new Error(`Render cancelled, page: ${pageNo}`);
}
};
const page = await this.getPage(pageNo);
checkCancelled();
const canvas = this.document.createElement('canvas');
canvas.classList.add('page-canvas');
const viewport = page.getViewport({ scale: scaledSize.scale || 1.0 });
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Could not get canvas context');
}
await page.render({
canvasContext: ctx,
viewport,
}).promise;
checkCancelled();
let textLayerDiv = null;
if (getTextLayer) {
textLayerDiv = this.document.createElement('div');
textLayerDiv.classList.add('textLayer');
const textFragment = this.document.createDocumentFragment();
const txtContext = await page.getTextContent();
checkCancelled();
// Pass the data to the method for rendering of text over the pdf canvas.
textLayerDiv.style.height = `${viewport.height}px`;
textLayerDiv.style.width = `${viewport.width}px`;
await pdfjsLib.renderTextLayer({
textContent: txtContext,
container: textFragment,
viewport: viewport,
textDivs: [],
}).promise;
textLayerDiv.appendChild(textFragment);
}
return { canvas, textLayerDiv };
};
public async renderPage(task: RenderRequest): Promise<RenderResult> {
// We're using a render mutex to avoid rendering too many pages at the same time
// Which can cause the pdfjs library to abandon some of the in-progress rendering unexpectedly
return await this.rendererMutex.runExclusive(async () => {
return await this.renderPageImpl(task);
});
}
public printPdf = () => {
const frame = this.document.createElement('iframe');
frame.style.position = 'fixed';
frame.style.display = 'none';
frame.style.height = '100%';
frame.style.width = '100%';
this.document.body.appendChild(frame);
frame.onload = () => {
frame.contentWindow.onafterprint = () => {
frame.remove();
};
frame.focus();
frame.contentWindow.print();
};
frame.src = this.url as string;
};
public downloadPdf = async () => {
const url = this.url as string;
const link = this.document.createElement('a');
link.href = url;
link.download = url;
link.click();
link.remove();
};
}