mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
453 lines
13 KiB
JavaScript
453 lines
13 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
|
|
|
|
function parsePageRanges(rangeString, totalPages) {
|
|
const pages = [];
|
|
const ranges = rangeString.split(',').map((r) => r.trim());
|
|
|
|
for (const range of ranges) {
|
|
if (range.includes('-')) {
|
|
const [start, end] = range.split('-').map((n) => parseInt(n.trim()));
|
|
for (let i = start; i <= end && i <= totalPages; i++) {
|
|
if (i > 0 && !pages.includes(i - 1)) {
|
|
pages.push(i - 1);
|
|
}
|
|
}
|
|
} else {
|
|
const page = parseInt(range);
|
|
if (page > 0 && page <= totalPages && !pages.includes(page - 1)) {
|
|
pages.push(page - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pages.sort((a, b) => a - b);
|
|
}
|
|
|
|
function hexToRgb(hex) {
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result
|
|
? {
|
|
r: parseInt(result[1], 16) / 255,
|
|
g: parseInt(result[2], 16) / 255,
|
|
b: parseInt(result[3], 16) / 255,
|
|
}
|
|
: { r: 0, g: 0, b: 0 };
|
|
}
|
|
|
|
async function pdfMerge(data) {
|
|
try {
|
|
const mergedPdf = await PDFDocument.create();
|
|
|
|
for (const filePath of data.inputFiles) {
|
|
const pdfBytes = fs.readFileSync(filePath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
|
copiedPages.forEach((page) => mergedPdf.addPage(page));
|
|
}
|
|
|
|
const pdfBytes = await mergedPdf.save();
|
|
fs.writeFileSync(data.outputPath, pdfBytes);
|
|
|
|
return { success: true, message: `Successfully merged ${data.inputFiles.length} PDFs` };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfSplit(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
const totalPages = pdf.getPageCount();
|
|
|
|
const splits = [];
|
|
|
|
if (data.splitMode === 'pages') {
|
|
const ranges = data.pageRanges.split(',').map((r) => r.trim());
|
|
for (let i = 0; i < ranges.length; i++) {
|
|
const range = ranges[i];
|
|
const pages = [];
|
|
|
|
if (range.includes('-')) {
|
|
const [start, end] = range.split('-').map((n) => parseInt(n.trim()));
|
|
for (let p = start; p <= end && p <= totalPages; p++) {
|
|
pages.push(p - 1);
|
|
}
|
|
} else {
|
|
const page = parseInt(range);
|
|
if (page > 0 && page <= totalPages) {
|
|
pages.push(page - 1);
|
|
}
|
|
}
|
|
|
|
if (pages.length > 0) {
|
|
splits.push({ pages, name: `part_${i + 1}` });
|
|
}
|
|
}
|
|
} else if (data.splitMode === 'interval') {
|
|
const interval = data.interval;
|
|
for (let i = 0; i < totalPages; i += interval) {
|
|
const pages = [];
|
|
for (let j = i; j < i + interval && j < totalPages; j++) {
|
|
pages.push(j);
|
|
}
|
|
splits.push({ pages, name: `part_${Math.floor(i / interval) + 1}` });
|
|
}
|
|
} else if (data.splitMode === 'size') {
|
|
const chunkSize = Math.max(1, Math.floor(totalPages / 5));
|
|
for (let i = 0; i < totalPages; i += chunkSize) {
|
|
const pages = [];
|
|
for (let j = i; j < i + chunkSize && j < totalPages; j++) {
|
|
pages.push(j);
|
|
}
|
|
splits.push({ pages, name: `part_${Math.floor(i / chunkSize) + 1}` });
|
|
}
|
|
}
|
|
|
|
const baseName = path.basename(data.inputPath, '.pdf');
|
|
for (const split of splits) {
|
|
const newPdf = await PDFDocument.create();
|
|
const copiedPages = await newPdf.copyPages(pdf, split.pages);
|
|
copiedPages.forEach((page) => newPdf.addPage(page));
|
|
|
|
const outputPath = path.join(data.outputFolder, `${baseName}_${split.name}.pdf`);
|
|
const newPdfBytes = await newPdf.save();
|
|
fs.writeFileSync(outputPath, newPdfBytes);
|
|
}
|
|
|
|
return { success: true, message: `Successfully split PDF into ${splits.length} files` };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfCompress(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
|
|
const compressedPdfBytes = await pdf.save({
|
|
useObjectStreams: true,
|
|
addDefaultPage: false,
|
|
objectsPerTick: 50,
|
|
});
|
|
|
|
fs.writeFileSync(data.outputPath, compressedPdfBytes);
|
|
|
|
const originalSize = fs.statSync(data.inputPath).size;
|
|
const compressedSize = fs.statSync(data.outputPath).size;
|
|
const savings = (((originalSize - compressedSize) / originalSize) * 100).toFixed(1);
|
|
|
|
return {
|
|
success: true,
|
|
message: `PDF compressed. Size reduced by ${savings}% (${(originalSize / 1024).toFixed(1)}KB → ${(compressedSize / 1024).toFixed(1)}KB)`,
|
|
};
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfRotate(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
const totalPages = pdf.getPageCount();
|
|
|
|
let pagesToRotate = [];
|
|
if (data.pages && data.pages.trim()) {
|
|
pagesToRotate = parsePageRanges(data.pages, totalPages);
|
|
} else {
|
|
pagesToRotate = Array.from({ length: totalPages }, (_, i) => i);
|
|
}
|
|
|
|
pagesToRotate.forEach((pageIndex) => {
|
|
const page = pdf.getPage(pageIndex);
|
|
page.setRotation(degrees(data.angle));
|
|
});
|
|
|
|
const rotatedPdfBytes = await pdf.save();
|
|
fs.writeFileSync(data.outputPath, rotatedPdfBytes);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Successfully rotated ${pagesToRotate.length} page(s) by ${data.angle}\u00B0`,
|
|
};
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfDeletePages(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
const totalPages = pdf.getPageCount();
|
|
|
|
const pagesToDelete = parsePageRanges(data.pages, totalPages);
|
|
|
|
pagesToDelete
|
|
.sort((a, b) => b - a)
|
|
.forEach((pageIndex) => {
|
|
pdf.removePage(pageIndex);
|
|
});
|
|
|
|
const newPdfBytes = await pdf.save();
|
|
fs.writeFileSync(data.outputPath, newPdfBytes);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Successfully deleted ${pagesToDelete.length} page(s). New PDF has ${totalPages - pagesToDelete.length} pages`,
|
|
};
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfReorder(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
const totalPages = pdf.getPageCount();
|
|
|
|
const newOrder = data.newOrder.split(',').map((n) => parseInt(n.trim()) - 1);
|
|
|
|
if (newOrder.length !== totalPages) {
|
|
return { success: false, error: `New order must include all ${totalPages} pages` };
|
|
}
|
|
|
|
const newPdf = await PDFDocument.create();
|
|
const copiedPages = await newPdf.copyPages(pdf, newOrder);
|
|
copiedPages.forEach((page) => newPdf.addPage(page));
|
|
|
|
const reorderedPdfBytes = await newPdf.save();
|
|
fs.writeFileSync(data.outputPath, reorderedPdfBytes);
|
|
|
|
return { success: true, message: 'Successfully reordered PDF pages' };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfWatermark(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
const totalPages = pdf.getPageCount();
|
|
|
|
let pagesToWatermark = [];
|
|
if (data.pages === 'all') {
|
|
pagesToWatermark = Array.from({ length: totalPages }, (_, i) => i);
|
|
} else if (data.pages === 'custom' && data.customPages) {
|
|
pagesToWatermark = parsePageRanges(data.customPages, totalPages);
|
|
}
|
|
|
|
const font = await pdf.embedFont(StandardFonts.Helvetica);
|
|
const color = hexToRgb(data.color);
|
|
|
|
for (const pageIndex of pagesToWatermark) {
|
|
const page = pdf.getPage(pageIndex);
|
|
const { width, height } = page.getSize();
|
|
|
|
let x,
|
|
y,
|
|
rotation = 0;
|
|
|
|
switch (data.position) {
|
|
case 'center':
|
|
x = width / 2;
|
|
y = height / 2;
|
|
break;
|
|
case 'diagonal':
|
|
x = width / 2;
|
|
y = height / 2;
|
|
rotation = 45;
|
|
break;
|
|
case 'top-left':
|
|
x = 50;
|
|
y = height - 50;
|
|
break;
|
|
case 'top-center':
|
|
x = width / 2;
|
|
y = height - 50;
|
|
break;
|
|
case 'top-right':
|
|
x = width - 50;
|
|
y = height - 50;
|
|
break;
|
|
case 'bottom-left':
|
|
x = 50;
|
|
y = 50;
|
|
break;
|
|
case 'bottom-center':
|
|
x = width / 2;
|
|
y = 50;
|
|
break;
|
|
case 'bottom-right':
|
|
x = width - 50;
|
|
y = 50;
|
|
break;
|
|
default:
|
|
x = width / 2;
|
|
y = height / 2;
|
|
}
|
|
|
|
page.drawText(data.text, {
|
|
x,
|
|
y,
|
|
size: data.fontSize,
|
|
font,
|
|
color: rgb(color.r, color.g, color.b),
|
|
opacity: data.opacity,
|
|
rotate: degrees(rotation),
|
|
});
|
|
}
|
|
|
|
const watermarkedPdfBytes = await pdf.save();
|
|
fs.writeFileSync(data.outputPath, watermarkedPdfBytes);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Successfully added watermark to ${pagesToWatermark.length} page(s)`,
|
|
};
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfEncrypt(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
|
|
const encryptedPdfBytes = await pdf.save({
|
|
userPassword: data.userPassword,
|
|
ownerPassword: data.ownerPassword || data.userPassword,
|
|
permissions: {
|
|
printing: data.permissions.printing ? 'highResolution' : 'lowResolution',
|
|
modifying: data.permissions.modifying,
|
|
copying: data.permissions.copying,
|
|
annotating: data.permissions.annotating,
|
|
fillingForms: data.permissions.fillingForms,
|
|
contentAccessibility: data.permissions.contentAccessibility,
|
|
documentAssembly: data.permissions.documentAssembly,
|
|
},
|
|
});
|
|
|
|
fs.writeFileSync(data.outputPath, encryptedPdfBytes);
|
|
|
|
return { success: true, message: 'Successfully added password protection to PDF' };
|
|
} catch (error) {
|
|
if (error.message.includes('encrypt') || error.message.includes('password')) {
|
|
return {
|
|
success: false,
|
|
error:
|
|
'PDF encryption requires pdf-lib with encryption support. This feature may not be available in the current version.',
|
|
};
|
|
}
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfDecrypt(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const pdf = await PDFDocument.load(pdfBytes, { password: data.password });
|
|
|
|
const decryptedPdfBytes = await pdf.save();
|
|
fs.writeFileSync(data.outputPath, decryptedPdfBytes);
|
|
|
|
return { success: true, message: 'Successfully removed password protection from PDF' };
|
|
} catch (error) {
|
|
if (error.message.includes('password') || error.message.includes('encrypted')) {
|
|
return { success: false, error: 'Incorrect password or PDF is not encrypted' };
|
|
}
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async function pdfSetPermissions(data) {
|
|
try {
|
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
|
const loadOptions = data.currentPassword ? { password: data.currentPassword } : {};
|
|
const pdf = await PDFDocument.load(pdfBytes, loadOptions);
|
|
|
|
const newPdfBytes = await pdf.save({
|
|
ownerPassword: data.ownerPassword,
|
|
permissions: {
|
|
printing: data.permissions.printing ? 'highResolution' : 'lowResolution',
|
|
modifying: data.permissions.modifying,
|
|
copying: data.permissions.copying,
|
|
annotating: data.permissions.annotating,
|
|
fillingForms: data.permissions.fillingForms,
|
|
contentAccessibility: data.permissions.contentAccessibility,
|
|
documentAssembly: data.permissions.documentAssembly,
|
|
},
|
|
});
|
|
|
|
fs.writeFileSync(data.outputPath, newPdfBytes);
|
|
|
|
return { success: true, message: 'Successfully updated PDF permissions' };
|
|
} catch (error) {
|
|
if (error.message.includes('encrypt') || error.message.includes('permission')) {
|
|
return {
|
|
success: false,
|
|
error:
|
|
'PDF permissions require pdf-lib with encryption support. This feature may not be available in the current version.',
|
|
};
|
|
}
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
function executeOperation(operation, data) {
|
|
switch (operation) {
|
|
case 'merge':
|
|
return pdfMerge(data);
|
|
case 'split':
|
|
return pdfSplit(data);
|
|
case 'compress':
|
|
return pdfCompress(data);
|
|
case 'rotate':
|
|
return pdfRotate(data);
|
|
case 'delete':
|
|
return pdfDeletePages(data);
|
|
case 'reorder':
|
|
return pdfReorder(data);
|
|
case 'watermark':
|
|
return pdfWatermark(data);
|
|
case 'encrypt':
|
|
return pdfEncrypt(data);
|
|
case 'decrypt':
|
|
return pdfDecrypt(data);
|
|
case 'permissions':
|
|
return pdfSetPermissions(data);
|
|
default:
|
|
return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
|
|
}
|
|
}
|
|
|
|
async function getPageCount(filePath) {
|
|
const pdfBytes = fs.readFileSync(filePath);
|
|
const pdf = await PDFDocument.load(pdfBytes);
|
|
return pdf.getPageCount();
|
|
}
|
|
|
|
module.exports = {
|
|
parsePageRanges,
|
|
hexToRgb,
|
|
pdfMerge,
|
|
pdfSplit,
|
|
pdfCompress,
|
|
pdfRotate,
|
|
pdfDeletePages,
|
|
pdfReorder,
|
|
pdfWatermark,
|
|
pdfEncrypt,
|
|
pdfDecrypt,
|
|
pdfSetPermissions,
|
|
executeOperation,
|
|
getPageCount,
|
|
};
|