mirror of
https://github.com/documenso/documenso.git
synced 2026-07-22 16:03:39 +10:00
feat: add pdf image renderer
This commit is contained in:
@@ -162,8 +162,8 @@ export const detectFieldsFromPdf = async ({
|
||||
// Mask existing fields on the image
|
||||
const maskedImage = await maskFieldsOnImage({
|
||||
image: page.image,
|
||||
width: page.width,
|
||||
height: page.height,
|
||||
width: page.scaledWidth,
|
||||
height: page.scaledHeight,
|
||||
fields: fieldsOnPage,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import pMap from 'p-map';
|
||||
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api';
|
||||
import type { ExportFormat } from 'skia-canvas';
|
||||
import { Canvas, Image, Path2D } from 'skia-canvas';
|
||||
|
||||
import { PDF_IMAGE_RENDER_SCALE } from '../../constants/pdf-viewer';
|
||||
|
||||
// @ts-expect-error napi-rs/canvas satisfies the requirements
|
||||
globalThis.Path2D = Path2D;
|
||||
// @ts-expect-error napi-rs/canvas satisfies the requirements
|
||||
@@ -42,10 +46,17 @@ class SkiaCanvasFactory {
|
||||
|
||||
export type PdfToImagesOptions = {
|
||||
scale?: number;
|
||||
|
||||
/**
|
||||
* The format of the images to return.
|
||||
*
|
||||
* Defaults to 'jpeg'.
|
||||
*/
|
||||
imageFormat?: ExportFormat;
|
||||
};
|
||||
|
||||
export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOptions = {}) => {
|
||||
const { scale = 2 } = options;
|
||||
const { scale = PDF_IMAGE_RENDER_SCALE, imageFormat = 'jpeg' } = options;
|
||||
|
||||
const task = await pdfjsLib.getDocument({
|
||||
data: pdfBytes,
|
||||
@@ -56,37 +67,7 @@ export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOpti
|
||||
|
||||
const images = await pMap(
|
||||
Array.from({ length: pdf.numPages }),
|
||||
async (_, index) => {
|
||||
const pageNumber = index + 1;
|
||||
const page = await pdf.getPage(pageNumber);
|
||||
|
||||
const viewport = page.getViewport({ scale });
|
||||
|
||||
const canvas = new Canvas(viewport.width, viewport.height);
|
||||
canvas.gpu = false;
|
||||
|
||||
const canvasContext = canvas.getContext('2d');
|
||||
|
||||
await page.render({
|
||||
// @ts-expect-error napi-rs/canvas satifies the requirements
|
||||
canvas,
|
||||
// @ts-expect-error napi-rs/canvas satifies the requirements
|
||||
canvasContext,
|
||||
viewport,
|
||||
}).promise;
|
||||
|
||||
const result = {
|
||||
pageNumber,
|
||||
image: await canvas.toBuffer('jpeg'),
|
||||
width: Math.floor(viewport.width),
|
||||
height: Math.floor(viewport.height),
|
||||
mimeType: 'image/jpeg',
|
||||
};
|
||||
|
||||
void page.cleanup();
|
||||
|
||||
return result;
|
||||
},
|
||||
async (_, pageIndex) => getPdfPageAsImage({ pdf, pageIndex, scale, imageFormat }),
|
||||
{ concurrency: 10 },
|
||||
);
|
||||
|
||||
@@ -95,3 +76,85 @@ export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOpti
|
||||
|
||||
return images;
|
||||
};
|
||||
|
||||
export type PdfToImageOptions = {
|
||||
scale?: number;
|
||||
pageIndex: number;
|
||||
|
||||
/**
|
||||
* The format of the image to return.
|
||||
* Defaults to 'jpeg'.
|
||||
*/
|
||||
imageFormat?: ExportFormat;
|
||||
};
|
||||
|
||||
export const pdfToImage = async (pdfBytes: Uint8Array, options: PdfToImageOptions) => {
|
||||
const { scale = PDF_IMAGE_RENDER_SCALE, pageIndex, imageFormat = 'jpeg' } = options;
|
||||
|
||||
if (pageIndex !== undefined && pageIndex < 0) {
|
||||
throw new Error('Page index must be greater than 0');
|
||||
}
|
||||
|
||||
const task = await pdfjsLib.getDocument({
|
||||
data: pdfBytes,
|
||||
CanvasFactory: SkiaCanvasFactory,
|
||||
});
|
||||
|
||||
const pdf = await task.promise;
|
||||
|
||||
const image = await getPdfPageAsImage({ pdf, pageIndex, scale, imageFormat });
|
||||
|
||||
void pdf.destroy().catch((e) => console.error(e));
|
||||
void task.destroy().catch((e) => console.error(e));
|
||||
|
||||
return image;
|
||||
};
|
||||
|
||||
type GetPdfPageAsImageOptions = {
|
||||
pdf: PDFDocumentProxy;
|
||||
pageIndex: number;
|
||||
scale: number;
|
||||
imageFormat: ExportFormat;
|
||||
};
|
||||
|
||||
const getPdfPageAsImage = async ({
|
||||
pdf,
|
||||
pageIndex,
|
||||
scale,
|
||||
imageFormat,
|
||||
}: GetPdfPageAsImageOptions) => {
|
||||
const page = await pdf.getPage(pageIndex + 1);
|
||||
|
||||
const viewport = page.getViewport({ scale });
|
||||
|
||||
const canvas = new Canvas(viewport.width, viewport.height);
|
||||
canvas.gpu = false;
|
||||
|
||||
const canvasContext = canvas.getContext('2d');
|
||||
|
||||
await page.render({
|
||||
// @ts-expect-error napi-rs/canvas satifies the requirements
|
||||
canvas,
|
||||
// @ts-expect-error napi-rs/canvas satifies the requirements
|
||||
canvasContext,
|
||||
viewport,
|
||||
}).promise;
|
||||
|
||||
const originalViewport = page.getViewport({ scale: 1 });
|
||||
|
||||
const result = {
|
||||
pageIndex,
|
||||
pageNumber: pageIndex + 1,
|
||||
image: await canvas.toBuffer(imageFormat),
|
||||
originalWidth: originalViewport.width,
|
||||
originalHeight: originalViewport.height,
|
||||
scale,
|
||||
scaledWidth: Math.floor(viewport.width),
|
||||
scaledHeight: Math.floor(viewport.height),
|
||||
mimeType: 'image/jpeg',
|
||||
};
|
||||
|
||||
void page.cleanup();
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -310,7 +310,7 @@ export const sendDocument = async ({
|
||||
|
||||
const injectFormValuesIntoDocument = async (
|
||||
envelope: Envelope,
|
||||
envelopeItem: Pick<EnvelopeItem, 'id'> & { documentData: DocumentData },
|
||||
envelopeItem: Pick<EnvelopeItem, 'id'> & { documentData: Omit<DocumentData, 'metadata'> },
|
||||
) => {
|
||||
const file = await getFileServerSide(envelopeItem.documentData);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user