feat: separate AGPL libraries and add dynamic WASM loading

- Add WASM settings page for configuring external AGPL modules
- Implement dynamic loading for PyMuPDF, Ghostscript, and CoherentPDF
- Add Cloudflare Worker proxy for serving WASM files with CORS
- Update all affected tool pages to check WASM availability
- Add showWasmRequiredDialog for missing module configuration

Documentation:
- Update README, licensing.html, and docs to clarify AGPL components
  are not bundled and must be configured separately
- Add WASM-PROXY.md deployment guide with recommended source URLs
- Rename "CPDF" to "CoherentPDF" for consistency
This commit is contained in:
alam00000
2026-01-27 15:26:11 +05:30
parent f6d432eaa7
commit 2c85ca74e9
75 changed files with 9696 additions and 6587 deletions

View File

@@ -5,6 +5,7 @@ interface AddAttachmentsMessage {
pdfBuffer: ArrayBuffer;
attachmentBuffers: ArrayBuffer[];
attachmentNames: string[];
cpdfUrl?: string;
}
interface AddAttachmentsSuccessResponse {
@@ -17,4 +18,6 @@ interface AddAttachmentsErrorResponse {
message: string;
}
type AddAttachmentsResponse = AddAttachmentsSuccessResponse | AddAttachmentsErrorResponse;
type AddAttachmentsResponse =
| AddAttachmentsSuccessResponse
| AddAttachmentsErrorResponse;

View File

@@ -1,13 +1,32 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function parsePageRange(rangeString, totalPages) {
const pages = new Set();
const parts = rangeString.split(',').map(s => s.trim());
const parts = rangeString.split(',').map((s) => s.trim());
for (const part of parts) {
if (part.includes('-')) {
const [start, end] = part.split('-').map(s => parseInt(s.trim(), 10));
const [start, end] = part.split('-').map((s) => parseInt(s.trim(), 10));
if (isNaN(start) || isNaN(end)) continue;
for (let i = Math.max(1, start); i <= Math.min(totalPages, end); i++) {
pages.add(i);
@@ -23,7 +42,13 @@ function parsePageRange(rangeString, totalPages) {
return Array.from(pages).sort((a, b) => a - b);
}
function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNames, attachmentLevel, pageRange) {
function addAttachmentsToPDFInWorker(
pdfBuffer,
attachmentBuffers,
attachmentNames,
attachmentLevel,
pageRange
) {
try {
const uint8Array = new Uint8Array(pdfBuffer);
@@ -33,18 +58,21 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
} catch (error) {
const errorMsg = error.message || error.toString();
if (errorMsg.includes('Failed to read PDF') ||
if (
errorMsg.includes('Failed to read PDF') ||
errorMsg.includes('Could not read object') ||
errorMsg.includes('No /Root entry') ||
errorMsg.includes('PDFError')) {
errorMsg.includes('PDFError')
) {
self.postMessage({
status: 'error',
message: 'The PDF file has structural issues and cannot be processed. The file may be corrupted, incomplete, or created with non-standard tools. Please try:\n\n• Opening and re-saving the PDF in another PDF viewer\n• Using a different PDF file\n• Repairing the PDF with a PDF repair tool'
message:
'The PDF file has structural issues and cannot be processed. The file may be corrupted, incomplete, or created with non-standard tools. Please try:\n\n• Opening and re-saving the PDF in another PDF viewer\n• Using a different PDF file\n• Repairing the PDF with a PDF repair tool',
});
} else {
self.postMessage({
status: 'error',
message: `Failed to load PDF: ${errorMsg}`
message: `Failed to load PDF: ${errorMsg}`,
});
}
return;
@@ -57,7 +85,7 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
if (!pageRange) {
self.postMessage({
status: 'error',
message: 'Page range is required for page-level attachments.'
message: 'Page range is required for page-level attachments.',
});
coherentpdf.deletePdf(pdf);
return;
@@ -66,7 +94,7 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
if (targetPages.length === 0) {
self.postMessage({
status: 'error',
message: 'Invalid page range specified.'
message: 'Invalid page range specified.',
});
coherentpdf.deletePdf(pdf);
return;
@@ -82,21 +110,25 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
coherentpdf.attachFileFromMemory(attachmentData, attachmentName, pdf);
} else {
for (const pageNum of targetPages) {
coherentpdf.attachFileToPageFromMemory(attachmentData, attachmentName, pdf, pageNum);
coherentpdf.attachFileToPageFromMemory(
attachmentData,
attachmentName,
pdf,
pageNum
);
}
}
} catch (error) {
console.warn(`Failed to attach file ${attachmentNames[i]}:`, error);
self.postMessage({
status: 'error',
message: `Failed to attach file ${attachmentNames[i]}: ${error.message || error}`
message: `Failed to attach file ${attachmentNames[i]}: ${error.message || error}`,
});
coherentpdf.deletePdf(pdf);
return;
}
}
// Save the modified PDF
const modifiedBytes = coherentpdf.toMemory(pdf, false, false);
coherentpdf.deletePdf(pdf);
@@ -105,22 +137,46 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
modifiedBytes.byteOffset + modifiedBytes.byteLength
);
self.postMessage({
status: 'success',
modifiedPDF: buffer
}, [buffer]);
self.postMessage(
{
status: 'success',
modifiedPDF: buffer,
},
[buffer]
);
} catch (error) {
self.postMessage({
status: 'error',
message: error instanceof Error
? error.message
: 'Unknown error occurred while adding attachments.'
message:
error instanceof Error
? error.message
: 'Unknown error occurred while adding attachments.',
});
}
}
self.onmessage = (e) => {
self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'add-attachments') {
addAttachmentsToPDFInWorker(
e.data.pdfBuffer,

View File

@@ -1,23 +1,24 @@
declare const coherentpdf: typeof import('../../src/types/coherentpdf.global').coherentpdf;
interface InterleaveFile {
name: string;
data: ArrayBuffer;
name: string;
data: ArrayBuffer;
}
interface InterleaveMessage {
command: 'interleave';
files: InterleaveFile[];
command: 'interleave';
files: InterleaveFile[];
cpdfUrl?: string;
}
interface InterleaveSuccessResponse {
status: 'success';
pdfBytes: ArrayBuffer;
status: 'success';
pdfBytes: ArrayBuffer;
}
interface InterleaveErrorResponse {
status: 'error';
message: string;
status: 'error';
message: string;
}
type InterleaveResponse = InterleaveSuccessResponse | InterleaveErrorResponse;

View File

@@ -1,64 +1,109 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
self.onmessage = function (e) {
const { command, files } = e.data;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
if (command === 'interleave') {
interleavePDFs(files);
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
self.onmessage = async function (e) {
const { command, files, cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (command === 'interleave') {
interleavePDFs(files);
}
};
function interleavePDFs(files) {
try {
const loadedPdfs = [];
const pageCounts = [];
try {
const loadedPdfs = [];
const pageCounts = [];
for (const file of files) {
const uint8Array = new Uint8Array(file.data);
const pdfDoc = coherentpdf.fromMemory(uint8Array, "");
loadedPdfs.push(pdfDoc);
pageCounts.push(coherentpdf.pages(pdfDoc));
}
if (loadedPdfs.length < 2) {
throw new Error('At least two PDF files are required for interleaving.');
}
const maxPages = Math.max(...pageCounts);
const pdfsToMerge = [];
const rangesToMerge = [];
for (let i = 1; i <= maxPages; i++) {
for (let j = 0; j < loadedPdfs.length; j++) {
if (i <= pageCounts[j]) {
pdfsToMerge.push(loadedPdfs[j]);
rangesToMerge.push(coherentpdf.range(i, i));
}
}
}
if (pdfsToMerge.length === 0) {
throw new Error('No valid pages to merge.');
}
const mergedPdf = coherentpdf.mergeSame(pdfsToMerge, true, true, rangesToMerge);
const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true);
const buffer = mergedPdfBytes.buffer;
coherentpdf.deletePdf(mergedPdf);
loadedPdfs.forEach(pdf => coherentpdf.deletePdf(pdf));
self.postMessage({
status: 'success',
pdfBytes: buffer
}, [buffer]);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message || 'Unknown error during interleave merge'
});
for (const file of files) {
const uint8Array = new Uint8Array(file.data);
const pdfDoc = coherentpdf.fromMemory(uint8Array, '');
loadedPdfs.push(pdfDoc);
pageCounts.push(coherentpdf.pages(pdfDoc));
}
if (loadedPdfs.length < 2) {
throw new Error('At least two PDF files are required for interleaving.');
}
const maxPages = Math.max(...pageCounts);
const pdfsToMerge = [];
const rangesToMerge = [];
for (let i = 1; i <= maxPages; i++) {
for (let j = 0; j < loadedPdfs.length; j++) {
if (i <= pageCounts[j]) {
pdfsToMerge.push(loadedPdfs[j]);
rangesToMerge.push(coherentpdf.range(i, i));
}
}
}
if (pdfsToMerge.length === 0) {
throw new Error('No valid pages to merge.');
}
const mergedPdf = coherentpdf.mergeSame(
pdfsToMerge,
true,
true,
rangesToMerge
);
const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true);
const buffer = mergedPdfBytes.buffer;
coherentpdf.deletePdf(mergedPdf);
loadedPdfs.forEach((pdf) => coherentpdf.deletePdf(pdf));
self.postMessage(
{
status: 'success',
pdfBytes: buffer,
},
[buffer]
);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message || 'Unknown error during interleave merge',
});
}
}

View File

@@ -4,6 +4,7 @@ interface GetAttachmentsMessage {
command: 'get-attachments';
fileBuffer: ArrayBuffer;
fileName: string;
cpdfUrl?: string;
}
interface EditAttachmentsMessage {
@@ -11,13 +12,21 @@ interface EditAttachmentsMessage {
fileBuffer: ArrayBuffer;
fileName: string;
attachmentsToRemove: number[];
cpdfUrl?: string;
}
type EditAttachmentsWorkerMessage = GetAttachmentsMessage | EditAttachmentsMessage;
type EditAttachmentsWorkerMessage =
| GetAttachmentsMessage
| EditAttachmentsMessage;
interface GetAttachmentsSuccessResponse {
status: 'success';
attachments: Array<{ index: number; name: string; page: number; data: ArrayBuffer }>;
attachments: Array<{
index: number;
name: string;
page: number;
data: ArrayBuffer;
}>;
fileName: string;
}
@@ -37,6 +46,12 @@ interface EditAttachmentsErrorResponse {
message: string;
}
type GetAttachmentsResponse = GetAttachmentsSuccessResponse | GetAttachmentsErrorResponse;
type EditAttachmentsResponse = EditAttachmentsSuccessResponse | EditAttachmentsErrorResponse;
type EditAttachmentsWorkerResponse = GetAttachmentsResponse | EditAttachmentsResponse;
type GetAttachmentsResponse =
| GetAttachmentsSuccessResponse
| GetAttachmentsErrorResponse;
type EditAttachmentsResponse =
| EditAttachmentsSuccessResponse
| EditAttachmentsErrorResponse;
type EditAttachmentsWorkerResponse =
| GetAttachmentsResponse
| EditAttachmentsResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
try {
@@ -11,7 +30,7 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
} catch (error) {
self.postMessage({
status: 'error',
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`,
});
return;
}
@@ -23,7 +42,7 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
self.postMessage({
status: 'success',
attachments: [],
fileName: fileName
fileName: fileName,
});
coherentpdf.deletePdf(pdf);
return;
@@ -37,13 +56,16 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
const attachmentData = coherentpdf.getAttachmentData(i);
const dataArray = new Uint8Array(attachmentData);
const buffer = dataArray.buffer.slice(dataArray.byteOffset, dataArray.byteOffset + dataArray.byteLength);
const buffer = dataArray.buffer.slice(
dataArray.byteOffset,
dataArray.byteOffset + dataArray.byteLength
);
attachments.push({
index: i,
name: String(name),
page: Number(page),
data: buffer
data: buffer,
});
} catch (error) {
console.warn(`Failed to get attachment ${i} from ${fileName}:`, error);
@@ -56,22 +78,27 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
const response = {
status: 'success',
attachments: attachments,
fileName: fileName
fileName: fileName,
};
const transferBuffers = attachments.map(att => att.data);
const transferBuffers = attachments.map((att) => att.data);
self.postMessage(response, transferBuffers);
} catch (error) {
self.postMessage({
status: 'error',
message: error instanceof Error
? error.message
: 'Unknown error occurred during attachment listing.'
message:
error instanceof Error
? error.message
: 'Unknown error occurred during attachment listing.',
});
}
}
function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove) {
function editAttachmentsInPDFInWorker(
fileBuffer,
fileName,
attachmentsToRemove
) {
try {
const uint8Array = new Uint8Array(fileBuffer);
@@ -81,7 +108,7 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
} catch (error) {
self.postMessage({
status: 'error',
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`,
});
return;
}
@@ -103,7 +130,7 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
attachmentsToKeep.push({
name: String(name),
page: Number(page),
data: dataCopy
data: dataCopy,
});
}
}
@@ -114,9 +141,18 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
for (const attachment of attachmentsToKeep) {
if (attachment.page === 0) {
coherentpdf.attachFileFromMemory(attachment.data, attachment.name, pdf);
coherentpdf.attachFileFromMemory(
attachment.data,
attachment.name,
pdf
);
} else {
coherentpdf.attachFileToPageFromMemory(attachment.data, attachment.name, pdf, attachment.page);
coherentpdf.attachFileToPageFromMemory(
attachment.data,
attachment.name,
pdf,
attachment.page
);
}
}
}
@@ -124,29 +160,58 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
const modifiedBytes = coherentpdf.toMemory(pdf, false, true);
coherentpdf.deletePdf(pdf);
const buffer = modifiedBytes.buffer.slice(modifiedBytes.byteOffset, modifiedBytes.byteOffset + modifiedBytes.byteLength);
const buffer = modifiedBytes.buffer.slice(
modifiedBytes.byteOffset,
modifiedBytes.byteOffset + modifiedBytes.byteLength
);
const response = {
status: 'success',
modifiedPDF: buffer,
fileName: fileName
fileName: fileName,
};
self.postMessage(response, [response.modifiedPDF]);
} catch (error) {
self.postMessage({
status: 'error',
message: error instanceof Error
? error.message
: 'Unknown error occurred during attachment editing.'
message:
error instanceof Error
? error.message
: 'Unknown error occurred during attachment editing.',
});
}
}
self.onmessage = (e) => {
self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'get-attachments') {
getAttachmentsFromPDFInWorker(e.data.fileBuffer, e.data.fileName);
} else if (e.data.command === 'edit-attachments') {
editAttachmentsInPDFInWorker(e.data.fileBuffer, e.data.fileName, e.data.attachmentsToRemove);
editAttachmentsInPDFInWorker(
e.data.fileBuffer,
e.data.fileName,
e.data.attachmentsToRemove
);
}
};
};

View File

@@ -4,6 +4,7 @@ interface ExtractAttachmentsMessage {
command: 'extract-attachments';
fileBuffers: ArrayBuffer[];
fileNames: string[];
cpdfUrl?: string;
}
interface ExtractAttachmentSuccessResponse {
@@ -16,4 +17,6 @@ interface ExtractAttachmentErrorResponse {
message: string;
}
type ExtractAttachmentResponse = ExtractAttachmentSuccessResponse | ExtractAttachmentErrorResponse;
type ExtractAttachmentResponse =
| ExtractAttachmentSuccessResponse
| ExtractAttachmentErrorResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
try {
@@ -37,7 +56,7 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
let uniqueName = attachmentName;
let counter = 1;
while (allAttachments.some(att => att.name === uniqueName)) {
while (allAttachments.some((att) => att.name === uniqueName)) {
const nameParts = attachmentName.split('.');
if (nameParts.length > 1) {
const extension = nameParts.pop();
@@ -56,10 +75,13 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
allAttachments.push({
name: uniqueName,
data: attachmentData.buffer.slice(0)
data: attachmentData.buffer.slice(0),
});
} catch (error) {
console.warn(`Failed to extract attachment ${j} from ${fileName}:`, error);
console.warn(
`Failed to extract attachment ${j} from ${fileName}:`,
error
);
}
}
@@ -70,21 +92,21 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
if (allAttachments.length === 0) {
self.postMessage({
status: 'error',
message: 'No attachments were found in the selected PDF(s).'
message: 'No attachments were found in the selected PDF(s).',
});
return;
}
const response = {
status: 'success',
attachments: []
attachments: [],
};
const transferBuffers = [];
for (const attachment of allAttachments) {
response.attachments.push({
name: attachment.name,
data: attachment.data
data: attachment.data,
});
transferBuffers.push(attachment.data);
}
@@ -93,15 +115,37 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
} catch (error) {
self.postMessage({
status: 'error',
message: error instanceof Error
? error.message
: 'Unknown error occurred during attachment extraction.'
message:
error instanceof Error
? error.message
: 'Unknown error occurred during attachment extraction.',
});
}
}
self.onmessage = (e) => {
self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'extract-attachments') {
extractAttachmentsFromPDFsInWorker(e.data.fileBuffers, e.data.fileNames);
}
};
};

View File

@@ -4,6 +4,7 @@ interface ConvertJSONToPDFMessage {
command: 'convert';
fileBuffers: ArrayBuffer[];
fileNames: string[];
cpdfUrl?: string;
}
interface JSONToPDFSuccessResponse {
@@ -17,4 +18,3 @@ interface JSONToPDFErrorResponse {
}
type JSONToPDFResponse = JSONToPDFSuccessResponse | JSONToPDFErrorResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
try {
@@ -15,13 +34,12 @@ function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
try {
pdf = coherentpdf.fromJSONMemory(uint8Array);
} catch (error) {
const errorMsg = error && error.message
? error.message
: 'Unknown error';
const errorMsg =
error && error.message ? error.message : 'Unknown error';
throw new Error(
`Failed to convert "${fileName}" to PDF. ` +
`The JSON file must be in the format produced by cpdf's outputJSONMemory. ` +
`Error: ${errorMsg}`
`The JSON file must be in the format produced by cpdf's outputJSONMemory. ` +
`Error: ${errorMsg}`
);
}
@@ -56,9 +74,29 @@ function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
}
}
self.onmessage = (e) => {
self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'convert') {
convertJSONsToPDFInWorker(e.data.fileBuffers, e.data.fileNames);
}
};

View File

@@ -1,33 +1,34 @@
declare const coherentpdf: typeof import('../../src/types/coherentpdf.global').coherentpdf;
interface MergeJob {
fileName: string;
rangeType: 'all' | 'specific' | 'single' | 'range';
rangeString?: string;
pageIndex?: number;
startPage?: number;
endPage?: number;
fileName: string;
rangeType: 'all' | 'specific' | 'single' | 'range';
rangeString?: string;
pageIndex?: number;
startPage?: number;
endPage?: number;
}
interface MergeFile {
name: string;
data: ArrayBuffer;
name: string;
data: ArrayBuffer;
}
interface MergeMessage {
command: 'merge';
files: MergeFile[];
jobs: MergeJob[];
command: 'merge';
files: MergeFile[];
jobs: MergeJob[];
cpdfUrl?: string;
}
interface MergeSuccessResponse {
status: 'success';
pdfBytes: ArrayBuffer;
status: 'success';
pdfBytes: ArrayBuffer;
}
interface MergeErrorResponse {
status: 'error';
message: string;
status: 'error';
message: string;
}
type MergeResponse = MergeSuccessResponse | MergeErrorResponse;

View File

@@ -1,71 +1,116 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
self.onmessage = function (e) {
const { command, files, jobs } = e.data;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
if (command === 'merge') {
mergePDFs(files, jobs);
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
self.onmessage = async function (e) {
const { command, files, jobs, cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (command === 'merge') {
mergePDFs(files, jobs);
}
};
function mergePDFs(files, jobs) {
try {
const loadedPdfs = {};
const pdfsToMerge = [];
const rangesToMerge = [];
try {
const loadedPdfs = {};
const pdfsToMerge = [];
const rangesToMerge = [];
for (const file of files) {
const uint8Array = new Uint8Array(file.data);
const pdfDoc = coherentpdf.fromMemory(uint8Array, "");
loadedPdfs[file.name] = pdfDoc;
}
for (const job of jobs) {
const sourcePdf = loadedPdfs[job.fileName];
if (!sourcePdf) continue;
let range;
if (job.rangeType === 'all') {
range = coherentpdf.all(sourcePdf);
} else if (job.rangeType === 'specific') {
if (coherentpdf.validatePagespec(job.rangeString)) {
range = coherentpdf.parsePagespec(sourcePdf, job.rangeString);
} else {
range = coherentpdf.all(sourcePdf);
}
} else if (job.rangeType === 'single') {
const pageNum = job.pageIndex + 1;
range = coherentpdf.range(pageNum, pageNum);
} else if (job.rangeType === 'range') {
range = coherentpdf.range(job.startPage, job.endPage);
}
pdfsToMerge.push(sourcePdf);
rangesToMerge.push(range);
}
if (pdfsToMerge.length === 0) {
throw new Error('No valid files or pages to merge.');
}
const mergedPdf = coherentpdf.mergeSame(pdfsToMerge, true, true, rangesToMerge);
const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true);
const buffer = mergedPdfBytes.buffer;
coherentpdf.deletePdf(mergedPdf);
Object.values(loadedPdfs).forEach(pdf => coherentpdf.deletePdf(pdf));
self.postMessage({
status: 'success',
pdfBytes: buffer
}, [buffer]);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message || 'Unknown error during merge'
});
for (const file of files) {
const uint8Array = new Uint8Array(file.data);
const pdfDoc = coherentpdf.fromMemory(uint8Array, '');
loadedPdfs[file.name] = pdfDoc;
}
for (const job of jobs) {
const sourcePdf = loadedPdfs[job.fileName];
if (!sourcePdf) continue;
let range;
if (job.rangeType === 'all') {
range = coherentpdf.all(sourcePdf);
} else if (job.rangeType === 'specific') {
if (coherentpdf.validatePagespec(job.rangeString)) {
range = coherentpdf.parsePagespec(sourcePdf, job.rangeString);
} else {
range = coherentpdf.all(sourcePdf);
}
} else if (job.rangeType === 'single') {
const pageNum = job.pageIndex + 1;
range = coherentpdf.range(pageNum, pageNum);
} else if (job.rangeType === 'range') {
range = coherentpdf.range(job.startPage, job.endPage);
}
pdfsToMerge.push(sourcePdf);
rangesToMerge.push(range);
}
if (pdfsToMerge.length === 0) {
throw new Error('No valid files or pages to merge.');
}
const mergedPdf = coherentpdf.mergeSame(
pdfsToMerge,
true,
true,
rangesToMerge
);
const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true);
const buffer = mergedPdfBytes.buffer;
coherentpdf.deletePdf(mergedPdf);
Object.values(loadedPdfs).forEach((pdf) => coherentpdf.deletePdf(pdf));
self.postMessage(
{
status: 'success',
pdfBytes: buffer,
},
[buffer]
);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message || 'Unknown error during merge',
});
}
}

View File

@@ -4,6 +4,7 @@ interface ConvertPDFToJSONMessage {
command: 'convert';
fileBuffers: ArrayBuffer[];
fileNames: string[];
cpdfUrl?: string;
}
interface PDFToJSONSuccessResponse {
@@ -17,4 +18,3 @@ interface PDFToJSONErrorResponse {
}
type PDFToJSONResponse = PDFToJSONSuccessResponse | PDFToJSONErrorResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
try {
@@ -12,8 +31,6 @@ function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
const uint8Array = new Uint8Array(buffer);
const pdf = coherentpdf.fromMemory(uint8Array, '');
//TODO:@ALAM -> add options for users to select these settings
// parse_content: true, no_stream_data: false, decompress_streams: false
const jsonData = coherentpdf.outputJSONMemory(true, false, false, pdf);
const jsonBuffer = jsonData.buffer.slice(0);
@@ -44,9 +61,29 @@ function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
}
}
self.onmessage = (e) => {
self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'convert') {
convertPDFsToJSONInWorker(e.data.fileBuffers, e.data.fileNames);
}
};

View File

@@ -7,6 +7,7 @@ interface GenerateTOCMessage {
fontSize: number;
fontFamily: number;
addBookmark: boolean;
cpdfUrl?: string;
}
interface TOCSuccessResponse {

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1);
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
let cpdfLoaded = false;
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function generateTableOfContentsInWorker(
pdfData,
@@ -49,7 +68,28 @@ function generateTableOfContentsInWorker(
}
}
self.onmessage = (e) => {
self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'generate-toc') {
generateTableOfContentsInWorker(
e.data.pdfData,