UNPKG

pdf-splitmerger

Version:
195 lines (189 loc) 8 kB
const { PDFDocument, degrees } = require("pdf-lib"); const remove = require("lodash/remove"); const range = require("lodash/range"); const min = require("lodash/min"); const max = require("lodash/max"); const maxBy = require("lodash/maxBy"); const sumBy = require("lodash/sumBy"); const { getDocument } = require("pdfjs-dist/webpack"); export const fileToDataURL = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve([reader.result, file.name]); reader.onerror = (error) => reject(error); }); }; export const dataURLtoFile = (dataurl, filename, type) => { let arr = dataurl.split(","), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } let imageFile = new File([u8arr], filename, { type: type ?? mime }); return imageFile; }; export const pdfPageNo = async (file) => { const pdfBytes = await file.arrayBuffer(); const pdfDoc = await PDFDocument.load(pdfBytes); const pages = pdfDoc.getPages(); return pages; }; //code to split to copy the given page into a new pdf file const pdfSplit = async (file, pageNo) => { const pdfBytes = await file.arrayBuffer(); const pdfDoc = await PDFDocument.load(pdfBytes); //Creating a new empty document const subDocument = await PDFDocument.create(); //getting the copiedpage const [copiedPage] = await subDocument.copyPages(pdfDoc, [pageNo - 1]); //Adding the page to the new pdf subDocument.addPage(copiedPage); const subPdfBytes = await subDocument.save(); //converting the bytes to file const newSplitedFile = new File( [subPdfBytes], `${file.name.split(".")[0]}.pdf`, { type: "application/pdf" } ); return newSplitedFile; }; const createCanvas = (viewport) => { let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); canvas.height = viewport.height; canvas.width = viewport.width; return { canvas, ctx }; }; //code to convert a pdf page to a canvas image data const pdfPageToCanvas = (pdfObjectUrl, pageno, scale = 1.5) => { //returning the imagedata with its height and width return new Promise((resolve, reject) => { //converting file to objecturl const fileObjectUrl = URL.createObjectURL(pdfObjectUrl); //using pdfjs-dist to read the pdf const thisdoc = getDocument(fileObjectUrl); //after the document is loaded thisdoc.promise.then((pdf) => { //using pdfjs-dist to get the page for processing pdf.getPage(pageno).then((page) => { //using pdfjs-dist to get viewport let viewport = page.getViewport({ scale }); //creating a canvas to render the pdf page let { canvas, ctx } = createCanvas({ height: viewport.height, width: viewport.width, }); let renderContext = { canvasContext: ctx, viewport: viewport }; const mypage = page.render(renderContext); //after the page is rendered into the canvas mypage.promise.then(() => { //using canvas getimagedata to convert the image into image data for merging this image with other image data const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const height = canvas.height; const width = canvas.width; resolve({ imageData, height, width }); }); }); }); }); }; const pdfSplitMerge = async (file, config) => { return pdfPageNo(file).then(async (page) => { //Getting the no of pages in the pdf const totalPage = page.length; //config eg: '1-4,7-8,9' meaning merge the given pages and split the rest into new pdf files //below code convert the above string into this format [[1,4],[7,8],[9]] const condArray = config .split(",") .reduce((prev, current) => [...prev, current.split("-")], []); //below code filter out the arrays with single value const mergeArray = condArray.filter((item) => item?.length > 1); //below code create an array of no from 1 to total pages eg:1->3 then [1,2,3] let splitArray = range(1, totalPage + 1); //Looping to merge pdf pages const mergeFileArray = Promise.all( mergeArray.map(async (item) => { //item=[1,3] //Looping item to get image data for each page to merge and remove the page no from split array //min and max is used to take the highest and lowest no in the array and create a range array to loop that many times //eg:item will become [1,2,3] const locValue = await Promise.all( range(min(item), parseInt(max(item)) + 1).map(async (val) => { //removing the merged pages from split array remove(splitArray, (e) => e == val); const imageData = await pdfPageToCanvas(file, val); return imageData; }) ); // after getting all the pages image data inthe locvalue array //create a canvas with height as the sum of all the heights and width will be the largest width //this is done to ajoin images vertically to join horizondally do opposite let { canvas, ctx } = createCanvas({ width: maxBy(locValue, "width").width, height: sumBy(locValue, "height"), }); //previous height is to determine the position of the image in the new canvas let prevHeight = 0; locValue.map((page, index) => { //putimage has 3 main arguments 1)imagedata 2)position from left 3)position from top //All images are positioned left thats why its zero for horizondal alignment it will be previouswidth ctx.putImageData(page.imageData, 0, prevHeight); //increasing the height so the next image goes below the fist prevHeight += locValue[index].height; }); //converting the canvas into a jpg file return dataURLtoFile( canvas.toDataURL(), `${file.name.split(".")[0]}.jpg`, "image/jpg" ); }) ); //After the merge is completed we get array of image file as 'res' return mergeFileArray.then(async (res) => { //looping the split array after the merge is complete because the remove is done inside the merge const splitFileArray = await Promise.all( splitArray.map(async (pageNo) => { //below code returns a pdf file with only the page in the pagno position const splitedPdf = await pdfSplit(file, pageNo); return splitedPdf; }) ); //After the merge and split is completed both array is spread into new array return [...res, ...splitFileArray]; }); }); }; export const downloadFile = (file) => { // Create a link and set the URL using `createObjectURL` const link = document.createElement("a"); link.style.display = "none"; link.href = URL.createObjectURL(file); link.download = file.name; // It needs to be added to the DOM so it can be clicked document.body.appendChild(link); link.click(); // To make this work on Firefox we need to wait // a little while before removing it. setTimeout(() => { URL.revokeObjectURL(link.href); link.parentNode.removeChild(link); }, 0); }; export const rotatePdf = async (file, angle = 0) => { const pdfBytes = await file.arrayBuffer(); const pdfDoc = await PDFDocument.load(pdfBytes); const pages = pdfDoc.getPages(); const angleLoc = 90 + angle; pages.map((page) => page.setRotation(degrees(angleLoc))); const pdfBytesRotated = await pdfDoc.save(); const newRotatedFile = new File([pdfBytesRotated], file.name, { type: "application/pdf", }); return [newRotatedFile, angleLoc]; }; export default pdfSplitMerge;