squash: feat: Create fillable PDF forms
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
hexToRgb,
|
||||
resetAndReloadTool,
|
||||
} from '../utils/helpers.js';
|
||||
import { state, resetState } from '../state.js';
|
||||
|
||||
@@ -21,7 +22,7 @@ export function setupWatermarkUI() {
|
||||
const imageOptions = document.getElementById('image-watermark-options');
|
||||
|
||||
watermarkTypeRadios.forEach((radio) => {
|
||||
radio.addEventListener('change', (e) => {
|
||||
radio.addEventListener('change', (e) => {
|
||||
if (e.target.value === 'text') {
|
||||
textOptions.classList.remove('hidden');
|
||||
imageOptions.classList.add('hidden');
|
||||
@@ -40,9 +41,9 @@ export function setupWatermarkUI() {
|
||||
opacitySliderText.addEventListener(
|
||||
'input',
|
||||
() =>
|
||||
(opacityValueText.textContent = (
|
||||
opacitySliderText as HTMLInputElement
|
||||
).value)
|
||||
(opacityValueText.textContent = (
|
||||
opacitySliderText as HTMLInputElement
|
||||
).value)
|
||||
);
|
||||
|
||||
angleSliderText.addEventListener(
|
||||
@@ -59,17 +60,17 @@ export function setupWatermarkUI() {
|
||||
opacitySliderImage.addEventListener(
|
||||
'input',
|
||||
() =>
|
||||
(opacityValueImage.textContent = (
|
||||
opacitySliderImage as HTMLInputElement
|
||||
).value)
|
||||
(opacityValueImage.textContent = (
|
||||
opacitySliderImage as HTMLInputElement
|
||||
).value)
|
||||
);
|
||||
|
||||
angleSliderImage.addEventListener(
|
||||
'input',
|
||||
() =>
|
||||
(angleValueImage.textContent = (
|
||||
angleSliderImage as HTMLInputElement
|
||||
).value)
|
||||
(angleValueImage.textContent = (
|
||||
angleSliderImage as HTMLInputElement
|
||||
).value)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,14 +176,7 @@ export async function addWatermark() {
|
||||
'watermarked.pdf'
|
||||
);
|
||||
|
||||
const toolid = state.activeTool;
|
||||
resetState();
|
||||
if (toolid) {
|
||||
const element = document.querySelector(
|
||||
`[data-tool-id="${toolid}"]`
|
||||
) as HTMLElement;
|
||||
if (element) element.click();
|
||||
}
|
||||
resetAndReloadTool();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showAlert(
|
||||
|
||||
@@ -7,11 +7,10 @@ import Sortable from 'sortablejs';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import '../../css/bookmark.css';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
import { truncateFilename, getPDFDocument } from '../utils/helpers.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
const modalContainer = document.getElementById('modal-container');
|
||||
|
||||
@@ -715,8 +714,10 @@ function handleResize() {
|
||||
if (window.innerWidth >= 1024) {
|
||||
viewerSection.classList.remove('hidden');
|
||||
bookmarksSection.classList.remove('hidden');
|
||||
showViewerBtn.classList.remove('bg-blue-50', 'text-blue-600');
|
||||
showBookmarksBtn.classList.remove('bg-blue-50', 'text-blue-600');
|
||||
showViewerBtn.classList.remove('bg-indigo-600', 'text-white');
|
||||
showViewerBtn.classList.add('text-gray-300');
|
||||
showBookmarksBtn.classList.remove('bg-indigo-600', 'text-white');
|
||||
showBookmarksBtn.classList.add('text-gray-300');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,15 +726,19 @@ window.addEventListener('resize', handleResize);
|
||||
showViewerBtn.addEventListener('click', () => {
|
||||
viewerSection.classList.remove('hidden');
|
||||
bookmarksSection.classList.add('hidden');
|
||||
showViewerBtn.classList.add('bg-blue-50', 'text-blue-600');
|
||||
showBookmarksBtn.classList.remove('bg-blue-50', 'text-blue-600');
|
||||
showViewerBtn.classList.add('bg-indigo-600', 'text-white');
|
||||
showViewerBtn.classList.remove('text-gray-300');
|
||||
showBookmarksBtn.classList.remove('bg-indigo-600', 'text-white');
|
||||
showBookmarksBtn.classList.add('text-gray-300');
|
||||
});
|
||||
|
||||
showBookmarksBtn.addEventListener('click', () => {
|
||||
viewerSection.classList.add('hidden');
|
||||
bookmarksSection.classList.remove('hidden');
|
||||
showBookmarksBtn.classList.add('bg-blue-50', 'text-blue-600');
|
||||
showViewerBtn.classList.remove('bg-blue-50', 'text-blue-600');
|
||||
showBookmarksBtn.classList.add('bg-indigo-600', 'text-white');
|
||||
showBookmarksBtn.classList.remove('text-gray-300');
|
||||
showViewerBtn.classList.remove('bg-indigo-600', 'text-white');
|
||||
showViewerBtn.classList.add('text-gray-300');
|
||||
});
|
||||
|
||||
// Dropdown toggles
|
||||
@@ -863,8 +868,10 @@ function resetToUploader() {
|
||||
// Reset mobile view
|
||||
viewerSection.classList.remove('hidden');
|
||||
bookmarksSection.classList.add('hidden');
|
||||
showViewerBtn.classList.add('bg-blue-50', 'text-blue-600');
|
||||
showBookmarksBtn.classList.remove('bg-blue-50', 'text-blue-600');
|
||||
showViewerBtn.classList.add('bg-indigo-600', 'text-white');
|
||||
showViewerBtn.classList.remove('text-gray-300');
|
||||
showBookmarksBtn.classList.remove('bg-indigo-600', 'text-white');
|
||||
showBookmarksBtn.classList.add('text-gray-300');
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
@@ -1033,7 +1040,7 @@ async function loadPDF(e) {
|
||||
if (!file) return;
|
||||
|
||||
originalFileName = file.name.replace('.pdf', '');
|
||||
filenameDisplay.textContent = originalFileName;
|
||||
filenameDisplay.textContent = truncateFilename(file.name);
|
||||
renderFileDisplay(file);
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
@@ -1046,7 +1053,7 @@ async function loadPDF(e) {
|
||||
|
||||
pdfLibDoc = await PDFDocument.load(arrayBuffer, { ignoreEncryption: true });
|
||||
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
const loadingTask = getPDFDocument({
|
||||
data: new Uint8Array(arrayBuffer),
|
||||
});
|
||||
pdfJsDoc = await loadingTask.promise;
|
||||
|
||||
@@ -3,10 +3,14 @@ import {
|
||||
downloadFile,
|
||||
hexToRgb,
|
||||
readFileAsArrayBuffer,
|
||||
getPDFDocument,
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
|
||||
let isRenderingPreview = false;
|
||||
let renderTimeout: any;
|
||||
@@ -16,29 +20,25 @@ async function updateTextColorPreview() {
|
||||
isRenderingPreview = true;
|
||||
|
||||
try {
|
||||
const textColorCanvas = document.getElementById('text-color-canvas');
|
||||
const textColorCanvas = document.getElementById('text-color-canvas') as HTMLCanvasElement;
|
||||
if (!textColorCanvas) return;
|
||||
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const page = await pdf.getPage(1); // Preview first page
|
||||
const viewport = page.getViewport({ scale: 0.8 });
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'getContext' does not exist on type 'HTML... Remove this comment to see the full error message
|
||||
const context = textColorCanvas.getContext('2d');
|
||||
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'width' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
textColorCanvas.width = viewport.width;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'height' does not exist on type 'HTMLElem... Remove this comment to see the full error message
|
||||
textColorCanvas.height = viewport.height;
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
await page.render({ canvasContext: context, viewport, canvas: textColorCanvas }).promise;
|
||||
const imageData = context.getImageData(
|
||||
0,
|
||||
0,
|
||||
(textColorCanvas as HTMLCanvasElement).width,
|
||||
(textColorCanvas as HTMLCanvasElement).height
|
||||
textColorCanvas.width,
|
||||
textColorCanvas.height
|
||||
);
|
||||
const data = imageData.data;
|
||||
const colorHex = (
|
||||
@@ -78,21 +78,19 @@ export async function setupTextColorTool() {
|
||||
renderTimeout = setTimeout(updateTextColorPreview, 250);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const page = await pdf.getPage(1);
|
||||
const viewport = page.getViewport({ scale: 0.8 });
|
||||
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'width' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
originalCanvas.width = viewport.width;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'height' does not exist on type 'HTMLElem... Remove this comment to see the full error message
|
||||
originalCanvas.height = viewport.height;
|
||||
(originalCanvas as HTMLCanvasElement).width = viewport.width;
|
||||
(originalCanvas as HTMLCanvasElement).height = viewport.height;
|
||||
|
||||
await page.render({
|
||||
canvasContext: (originalCanvas as HTMLCanvasElement).getContext('2d'),
|
||||
viewport,
|
||||
canvas: originalCanvas as HTMLCanvasElement,
|
||||
}).promise;
|
||||
await updateTextColorPreview();
|
||||
}
|
||||
@@ -103,16 +101,14 @@ export async function changeTextColor() {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
const colorHex = document.getElementById('text-color-input').value;
|
||||
const colorHex = (document.getElementById('text-color-input') as HTMLInputElement).value;
|
||||
const { r, g, b } = hexToRgb(colorHex);
|
||||
const darknessThreshold = 120;
|
||||
|
||||
showLoader('Changing text color...');
|
||||
try {
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
|
||||
@@ -126,7 +122,7 @@ export async function changeTextColor() {
|
||||
canvas.height = viewport.height;
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
await page.render({ canvasContext: context, viewport, canvas }).promise;
|
||||
|
||||
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = imageData.data;
|
||||
@@ -144,16 +140,15 @@ export async function changeTextColor() {
|
||||
}
|
||||
context.putImageData(imageData, 0, 0);
|
||||
|
||||
const pngImageBytes = await new Promise((resolve) =>
|
||||
const pngImageBytes = await new Promise<Uint8Array>((resolve) =>
|
||||
canvas.toBlob((blob) => {
|
||||
const reader = new FileReader();
|
||||
// @ts-expect-error TS(2769) FIXME: No overload matches this call.
|
||||
reader.onload = () => resolve(new Uint8Array(reader.result));
|
||||
reader.readAsArrayBuffer(blob);
|
||||
reader.onload = () => resolve(new Uint8Array(reader.result as ArrayBuffer));
|
||||
reader.readAsArrayBuffer(blob!);
|
||||
}, 'image/png')
|
||||
);
|
||||
|
||||
const pngImage = await newPdfDoc.embedPng(pngImageBytes as ArrayBuffer);
|
||||
const pngImage = await newPdfDoc.embedPng(pngImageBytes);
|
||||
const newPage = newPdfDoc.addPage([viewport.width, viewport.height]);
|
||||
newPage.drawImage(pngImage, {
|
||||
x: 0,
|
||||
|
||||
@@ -1,67 +1,208 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, hexToRgb } from '../utils/helpers.js';
|
||||
import { downloadFile, hexToRgb, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
import { PDFDocument as PDFLibDocument, rgb } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
document.addEventListener('change', (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.id === 'add-separator') {
|
||||
const separatorOptions = document.getElementById('separator-options');
|
||||
if (separatorOptions) {
|
||||
const checkbox = target as HTMLInputElement;
|
||||
if (checkbox.checked) {
|
||||
separatorOptions.classList.remove('hidden');
|
||||
} else {
|
||||
separatorOptions.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function combineToSinglePage() {
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
const orientation = document.getElementById('combine-orientation').value;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
const spacing = parseInt(document.getElementById('page-spacing').value) || 0;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
const backgroundColorHex = document.getElementById('background-color').value;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'checked' does not exist on type 'HTMLEle... Remove this comment to see the full error message
|
||||
const addSeparator = document.getElementById('add-separator').checked;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
const separatorThickness = parseFloat(document.getElementById('separator-thickness').value) || 0.5;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
const separatorColorHex = document.getElementById('separator-color').value;
|
||||
|
||||
const backgroundColor = hexToRgb(backgroundColorHex);
|
||||
const separatorColor = hexToRgb(separatorColorHex);
|
||||
|
||||
showLoader('Combining pages...');
|
||||
try {
|
||||
const sourceDoc = state.pdfDoc;
|
||||
const newDoc = await PDFLibDocument.create();
|
||||
const sourcePages = sourceDoc.getPages();
|
||||
|
||||
const pdfBytes = await sourceDoc.save();
|
||||
const pdfjsDoc = await getPDFDocument({ data: pdfBytes }).promise;
|
||||
|
||||
const sourcePages = sourceDoc.getPages();
|
||||
let maxWidth = 0;
|
||||
let maxHeight = 0;
|
||||
let totalWidth = 0;
|
||||
let totalHeight = 0;
|
||||
|
||||
sourcePages.forEach((page: any) => {
|
||||
const { width, height } = page.getSize();
|
||||
if (width > maxWidth) maxWidth = width;
|
||||
if (height > maxHeight) maxHeight = height;
|
||||
totalWidth += width;
|
||||
totalHeight += height;
|
||||
});
|
||||
totalHeight += Math.max(0, sourcePages.length - 1) * spacing;
|
||||
|
||||
const newPage = newDoc.addPage([maxWidth, totalHeight]);
|
||||
let finalWidth, finalHeight;
|
||||
if (orientation === 'horizontal') {
|
||||
finalWidth = totalWidth + Math.max(0, sourcePages.length - 1) * spacing;
|
||||
finalHeight = maxHeight;
|
||||
} else {
|
||||
finalWidth = maxWidth;
|
||||
finalHeight = totalHeight + Math.max(0, sourcePages.length - 1) * spacing;
|
||||
}
|
||||
|
||||
const newPage = newDoc.addPage([finalWidth, finalHeight]);
|
||||
|
||||
if (backgroundColorHex.toUpperCase() !== '#FFFFFF') {
|
||||
newPage.drawRectangle({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: maxWidth,
|
||||
height: totalHeight,
|
||||
width: finalWidth,
|
||||
height: finalHeight,
|
||||
color: rgb(backgroundColor.r, backgroundColor.g, backgroundColor.b),
|
||||
});
|
||||
}
|
||||
|
||||
let currentY = totalHeight;
|
||||
let currentX = 0;
|
||||
let currentY = finalHeight;
|
||||
|
||||
for (let i = 0; i < sourcePages.length; i++) {
|
||||
const sourcePage = sourcePages[i];
|
||||
const { width, height } = sourcePage.getSize();
|
||||
const embeddedPage = await newDoc.embedPage(sourcePage);
|
||||
|
||||
currentY -= height;
|
||||
const x = (maxWidth - width) / 2;
|
||||
try {
|
||||
const page = await pdfjsDoc.getPage(i + 1);
|
||||
const scale = 2.0;
|
||||
const viewport = page.getViewport({ scale });
|
||||
|
||||
newPage.drawPage(embeddedPage, { x, y: currentY, width, height });
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
const context = canvas.getContext('2d')!;
|
||||
|
||||
if (addSeparator && i < sourcePages.length - 1) {
|
||||
const lineY = currentY - spacing / 2;
|
||||
newPage.drawLine({
|
||||
start: { x: 0, y: lineY },
|
||||
end: { x: maxWidth, y: lineY },
|
||||
thickness: 0.5,
|
||||
color: rgb(0.8, 0.8, 0.8),
|
||||
});
|
||||
await page.render({
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
canvas
|
||||
}).promise;
|
||||
|
||||
const pngDataUrl = canvas.toDataURL('image/png');
|
||||
const pngImage = await newDoc.embedPng(pngDataUrl);
|
||||
|
||||
if (orientation === 'horizontal') {
|
||||
const y = (finalHeight - height) / 2;
|
||||
newPage.drawImage(pngImage, { x: currentX, y, width, height });
|
||||
} else {
|
||||
// Vertical layout: stack top to bottom
|
||||
currentY -= height;
|
||||
const x = (finalWidth - width) / 2; // Center horizontally
|
||||
newPage.drawImage(pngImage, { x, y: currentY, width, height });
|
||||
}
|
||||
} catch (renderError) {
|
||||
console.warn(`Failed to render page ${i + 1} with PDF.js, trying fallback method:`, renderError);
|
||||
|
||||
// Fallback: try to copy and embed the page directly
|
||||
try {
|
||||
const [copiedPage] = await newDoc.copyPages(sourceDoc, [i]);
|
||||
|
||||
if (orientation === 'horizontal') {
|
||||
const y = (finalHeight - height) / 2;
|
||||
const embeddedPage = await newDoc.embedPage(copiedPage);
|
||||
newPage.drawPage(embeddedPage, { x: currentX, y, width, height });
|
||||
} else {
|
||||
currentY -= height;
|
||||
const x = (finalWidth - width) / 2;
|
||||
const embeddedPage = await newDoc.embedPage(copiedPage);
|
||||
newPage.drawPage(embeddedPage, { x, y: currentY, width, height });
|
||||
}
|
||||
} catch (embedError) {
|
||||
console.error(`Failed to process page ${i + 1}:`, embedError);
|
||||
|
||||
if (orientation === 'horizontal') {
|
||||
const y = (finalHeight - height) / 2;
|
||||
newPage.drawRectangle({
|
||||
x: currentX,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
borderColor: rgb(0.8, 0, 0),
|
||||
borderWidth: 2,
|
||||
});
|
||||
|
||||
newPage.drawText(`Page ${i + 1} could not be rendered`, {
|
||||
x: currentX + 10,
|
||||
y: y + height / 2,
|
||||
size: 12,
|
||||
color: rgb(0.8, 0, 0),
|
||||
});
|
||||
} else {
|
||||
currentY -= height;
|
||||
const x = (finalWidth - width) / 2;
|
||||
newPage.drawRectangle({
|
||||
x,
|
||||
y: currentY,
|
||||
width,
|
||||
height,
|
||||
borderColor: rgb(0.8, 0, 0),
|
||||
borderWidth: 2,
|
||||
});
|
||||
|
||||
newPage.drawText(`Page ${i + 1} could not be rendered`, {
|
||||
x: x + 10,
|
||||
y: currentY + height / 2,
|
||||
size: 12,
|
||||
color: rgb(0.8, 0, 0),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentY -= spacing;
|
||||
// Draw separator line
|
||||
if (addSeparator && i < sourcePages.length - 1) {
|
||||
if (orientation === 'horizontal') {
|
||||
const lineX = currentX + width + spacing / 2;
|
||||
newPage.drawLine({
|
||||
start: { x: lineX, y: 0 },
|
||||
end: { x: lineX, y: finalHeight },
|
||||
thickness: separatorThickness,
|
||||
color: rgb(separatorColor.r, separatorColor.g, separatorColor.b),
|
||||
});
|
||||
currentX += width + spacing;
|
||||
} else {
|
||||
const lineY = currentY - spacing / 2;
|
||||
newPage.drawLine({
|
||||
start: { x: 0, y: lineY },
|
||||
end: { x: finalWidth, y: lineY },
|
||||
thickness: separatorThickness,
|
||||
color: rgb(separatorColor.r, separatorColor.g, separatorColor.b),
|
||||
});
|
||||
currentY -= spacing;
|
||||
}
|
||||
} else {
|
||||
if (orientation === 'horizontal') {
|
||||
currentX += width + spacing;
|
||||
} else {
|
||||
currentY -= spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newPdfBytes = await newDoc.save();
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
|
||||
const state = {
|
||||
pdfDoc1: null,
|
||||
@@ -122,8 +126,7 @@ async function setupFileInput(inputId: any, docKey: any, displayId: any) {
|
||||
try {
|
||||
showLoader(`Loading ${file.name}...`);
|
||||
const pdfBytes = await readFileAsArrayBuffer(file);
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
state[docKey] = await pdfjsLib.getDocument(pdfBytes).promise;
|
||||
state[docKey] = await getPDFDocument(pdfBytes).promise;
|
||||
|
||||
if (state.pdfDoc1 && state.pdfDoc2) {
|
||||
document.getElementById('compare-viewer').classList.remove('hidden');
|
||||
|
||||
@@ -3,12 +3,14 @@ import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
getPDFDocument,
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
import { PDFDocument, PDFName, PDFDict, PDFStream, PDFNumber } from 'pdf-lib';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
function dataUrlToBytes(dataUrl: any) {
|
||||
const base64 = dataUrl.split(',')[1];
|
||||
const binaryString = atob(base64);
|
||||
@@ -70,8 +72,8 @@ async function performSmartCompression(arrayBuffer: any, settings: any) {
|
||||
const bitsPerComponent =
|
||||
stream.dict.get(PDFName.of('BitsPerComponent')) instanceof PDFNumber
|
||||
? (
|
||||
stream.dict.get(PDFName.of('BitsPerComponent')) as PDFNumber
|
||||
).asNumber()
|
||||
stream.dict.get(PDFName.of('BitsPerComponent')) as PDFNumber
|
||||
).asNumber()
|
||||
: 8;
|
||||
|
||||
if (width > 0 && height > 0) {
|
||||
@@ -183,12 +185,7 @@ async function performSmartCompression(arrayBuffer: any, settings: any) {
|
||||
}
|
||||
|
||||
async function performLegacyCompression(arrayBuffer: any, settings: any) {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
const pdfJsDoc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||||
const pdfJsDoc = await getPDFDocument({ data: arrayBuffer }).promise;
|
||||
const newPdfDoc = await PDFDocument.create();
|
||||
|
||||
for (let i = 1; i <= pdfJsDoc.numPages; i++) {
|
||||
@@ -324,13 +321,13 @@ export async function compress() {
|
||||
showAlert(
|
||||
'Compression Complete',
|
||||
`Method: **${usedMethod}**. ` +
|
||||
`File size reduced from ${originalSize} to ${compressedSize} (Saved ${savingsPercent}%).`
|
||||
`File size reduced from ${originalSize} to ${compressedSize} (Saved ${savingsPercent}%).`
|
||||
);
|
||||
} else {
|
||||
showAlert(
|
||||
'Compression Finished',
|
||||
`Method: **${usedMethod}**. ` +
|
||||
`Could not reduce file size. Original: ${originalSize}, New: ${compressedSize}.`,
|
||||
`Could not reduce file size. Original: ${originalSize}, New: ${compressedSize}.`,
|
||||
// @ts-expect-error TS(2554) FIXME: Expected 2 arguments, but got 3.
|
||||
'warning'
|
||||
);
|
||||
@@ -384,13 +381,13 @@ export async function compress() {
|
||||
showAlert(
|
||||
'Compression Complete',
|
||||
`Compressed ${state.files.length} PDF(s). ` +
|
||||
`Total size reduced from ${formatBytes(totalOriginalSize)} to ${formatBytes(totalCompressedSize)} (Saved ${totalSavingsPercent}%).`
|
||||
`Total size reduced from ${formatBytes(totalOriginalSize)} to ${formatBytes(totalCompressedSize)} (Saved ${totalSavingsPercent}%).`
|
||||
);
|
||||
} else {
|
||||
showAlert(
|
||||
'Compression Finished',
|
||||
`Compressed ${state.files.length} PDF(s). ` +
|
||||
`Total size: ${formatBytes(totalCompressedSize)}.`
|
||||
`Total size: ${formatBytes(totalCompressedSize)}.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import Cropper from 'cropperjs';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
// --- Global State for the Cropper Tool ---
|
||||
const cropperState = {
|
||||
pdfDoc: null,
|
||||
@@ -129,45 +131,44 @@ function enableControls() {
|
||||
*/
|
||||
async function performMetadataCrop(pdfToModify: any, cropData: any) {
|
||||
for (const pageNum in cropData) {
|
||||
// @ts-expect-error TS(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
|
||||
const page = pdfToModify.getPages()[pageNum - 1];
|
||||
const { width: pageWidth, height: pageHeight } = page.getSize();
|
||||
const rotation = page.getRotation().angle;
|
||||
const pdfJsPage = await cropperState.pdfDoc.getPage(Number(pageNum));
|
||||
const viewport = pdfJsPage.getViewport({ scale: 1 });
|
||||
|
||||
const crop = cropData[pageNum];
|
||||
|
||||
const visualPdfWidth = pageWidth * crop.width;
|
||||
const visualPdfHeight = pageHeight * crop.height;
|
||||
const visualPdfX = pageWidth * crop.x;
|
||||
const visualPdfY = pageHeight * crop.y;
|
||||
// Man I hate doing math
|
||||
// Calculate visual crop rectangle in viewport pixels
|
||||
const cropX = viewport.width * crop.x;
|
||||
const cropY = viewport.height * crop.y;
|
||||
const cropW = viewport.width * crop.width;
|
||||
const cropH = viewport.height * crop.height;
|
||||
|
||||
let finalX, finalY, finalWidth, finalHeight;
|
||||
switch (rotation) {
|
||||
case 90:
|
||||
finalX = visualPdfY;
|
||||
finalY = pageWidth - visualPdfX - visualPdfWidth;
|
||||
finalWidth = visualPdfHeight;
|
||||
finalHeight = visualPdfWidth;
|
||||
break;
|
||||
case 180:
|
||||
finalX = pageWidth - visualPdfX - visualPdfWidth;
|
||||
finalY = pageHeight - visualPdfY - visualPdfHeight;
|
||||
finalWidth = visualPdfWidth;
|
||||
finalHeight = visualPdfHeight;
|
||||
break;
|
||||
case 270:
|
||||
finalX = pageHeight - visualPdfY - visualPdfHeight;
|
||||
finalY = visualPdfX;
|
||||
finalWidth = visualPdfHeight;
|
||||
finalHeight = visualPdfWidth;
|
||||
break;
|
||||
default:
|
||||
finalX = visualPdfX;
|
||||
finalY = pageHeight - visualPdfY - visualPdfHeight;
|
||||
finalWidth = visualPdfWidth;
|
||||
finalHeight = visualPdfHeight;
|
||||
break;
|
||||
}
|
||||
page.setCropBox(finalX, finalY, finalWidth, finalHeight);
|
||||
// Define the 4 corners of the crop rectangle in visual coordinates (Top-Left origin)
|
||||
const visualCorners = [
|
||||
{ x: cropX, y: cropY }, // TL
|
||||
{ x: cropX + cropW, y: cropY }, // TR
|
||||
{ x: cropX + cropW, y: cropY + cropH }, // BR
|
||||
{ x: cropX, y: cropY + cropH }, // BL
|
||||
];
|
||||
|
||||
// This handles rotation, media box offsets, and coordinate system flips automatically
|
||||
const pdfCorners = visualCorners.map(p => {
|
||||
return viewport.convertToPdfPoint(p.x, p.y);
|
||||
});
|
||||
|
||||
// Find the bounding box of the converted points in PDF coordinates
|
||||
// convertToPdfPoint returns [x, y] arrays
|
||||
const pdfXs = pdfCorners.map(p => p[0]);
|
||||
const pdfYs = pdfCorners.map(p => p[1]);
|
||||
|
||||
const minX = Math.min(...pdfXs);
|
||||
const maxX = Math.max(...pdfXs);
|
||||
const minY = Math.min(...pdfYs);
|
||||
const maxY = Math.max(...pdfYs);
|
||||
|
||||
// @ts-expect-error TS(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
|
||||
const page = pdfToModify.getPages()[pageNum - 1];
|
||||
page.setCropBox(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,107 +243,104 @@ export async function setupCropperTool() {
|
||||
if (state.files.length === 0) return;
|
||||
|
||||
// Clear pageCrops on new file upload
|
||||
cropperState.pageCrops = {};
|
||||
|
||||
const arrayBuffer = await readFileAsArrayBuffer(state.files[0]);
|
||||
cropperState.originalPdfBytes = arrayBuffer;
|
||||
const arrayBufferForPdfJs = (arrayBuffer as ArrayBuffer).slice(0);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
try {
|
||||
const loadingTask = pdfjsLib.getDocument({ data: arrayBufferForPdfJs });
|
||||
// Clear pageCrops on new file upload
|
||||
cropperState.pageCrops = {};
|
||||
|
||||
const arrayBuffer = await readFileAsArrayBuffer(state.files[0]);
|
||||
cropperState.originalPdfBytes = arrayBuffer;
|
||||
const arrayBufferForPdfJs = (arrayBuffer as ArrayBuffer).slice(0);
|
||||
const loadingTask = getPDFDocument({ data: arrayBufferForPdfJs });
|
||||
|
||||
cropperState.pdfDoc = await loadingTask.promise;
|
||||
cropperState.currentPageNum = 1;
|
||||
|
||||
await displayPageAsImage(cropperState.currentPageNum);
|
||||
|
||||
document
|
||||
.getElementById('prev-page')
|
||||
.addEventListener('click', () => changePage(-1));
|
||||
document
|
||||
.getElementById('next-page')
|
||||
.addEventListener('click', () => changePage(1));
|
||||
|
||||
document
|
||||
.getElementById('crop-button')
|
||||
.addEventListener('click', async () => {
|
||||
// Get the last known crop from the active page before processing
|
||||
saveCurrentCrop();
|
||||
|
||||
const isDestructive = (
|
||||
document.getElementById('destructive-crop-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
const isApplyToAll = (
|
||||
document.getElementById('apply-to-all-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
|
||||
let finalCropData = {};
|
||||
if (isApplyToAll) {
|
||||
const currentCrop =
|
||||
cropperState.pageCrops[cropperState.currentPageNum];
|
||||
if (!currentCrop) {
|
||||
showAlert('No Crop Area', 'Please select an area to crop first.');
|
||||
return;
|
||||
}
|
||||
// Apply the active page's crop to all pages
|
||||
for (let i = 1; i <= cropperState.pdfDoc.numPages; i++) {
|
||||
finalCropData[i] = currentCrop;
|
||||
}
|
||||
} else {
|
||||
// If not applying to all, only process pages with saved crops
|
||||
finalCropData = Object.keys(cropperState.pageCrops).reduce(
|
||||
(obj, key) => {
|
||||
obj[key] = cropperState.pageCrops[key];
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(finalCropData).length === 0) {
|
||||
showAlert(
|
||||
'No Crop Area',
|
||||
'Please select an area on at least one page to crop.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
showLoader('Applying crop...');
|
||||
|
||||
try {
|
||||
let finalPdfBytes;
|
||||
if (isDestructive) {
|
||||
const newPdfDoc = await performFlatteningCrop(finalCropData);
|
||||
finalPdfBytes = await newPdfDoc.save();
|
||||
} else {
|
||||
const pdfToModify = await PDFLibDocument.load(
|
||||
cropperState.originalPdfBytes
|
||||
);
|
||||
await performMetadataCrop(pdfToModify, finalCropData);
|
||||
finalPdfBytes = await pdfToModify.save();
|
||||
}
|
||||
|
||||
const fileName = isDestructive
|
||||
? 'flattened_crop.pdf'
|
||||
: 'standard_crop.pdf';
|
||||
downloadFile(
|
||||
new Blob([finalPdfBytes], { type: 'application/pdf' }),
|
||||
fileName
|
||||
);
|
||||
showAlert('Success', 'Crop complete! Your download has started.');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showAlert('Error', 'An error occurred during cropping.');
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error setting up cropper tool:', error);
|
||||
showAlert('Error', 'Failed to load PDF for cropping.');
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById('prev-page')
|
||||
.addEventListener('click', () => changePage(-1));
|
||||
document
|
||||
.getElementById('next-page')
|
||||
.addEventListener('click', () => changePage(1));
|
||||
|
||||
document
|
||||
.getElementById('crop-button')
|
||||
.addEventListener('click', async () => {
|
||||
// Get the last known crop from the active page before processing
|
||||
saveCurrentCrop();
|
||||
|
||||
const isDestructive = (
|
||||
document.getElementById('destructive-crop-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
const isApplyToAll = (
|
||||
document.getElementById('apply-to-all-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
|
||||
let finalCropData = {};
|
||||
if (isApplyToAll) {
|
||||
const currentCrop =
|
||||
cropperState.pageCrops[cropperState.currentPageNum];
|
||||
if (!currentCrop) {
|
||||
showAlert('No Crop Area', 'Please select an area to crop first.');
|
||||
return;
|
||||
}
|
||||
// Apply the active page's crop to all pages
|
||||
for (let i = 1; i <= cropperState.pdfDoc.numPages; i++) {
|
||||
finalCropData[i] = currentCrop;
|
||||
}
|
||||
} else {
|
||||
// If not applying to all, only process pages with saved crops
|
||||
finalCropData = Object.keys(cropperState.pageCrops).reduce(
|
||||
(obj, key) => {
|
||||
obj[key] = cropperState.pageCrops[key];
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(finalCropData).length === 0) {
|
||||
showAlert(
|
||||
'No Crop Area',
|
||||
'Please select an area on at least one page to crop.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
showLoader('Applying crop...');
|
||||
|
||||
try {
|
||||
let finalPdfBytes;
|
||||
if (isDestructive) {
|
||||
const newPdfDoc = await performFlatteningCrop(finalCropData);
|
||||
finalPdfBytes = await newPdfDoc.save();
|
||||
} else {
|
||||
const pdfToModify = await PDFLibDocument.load(
|
||||
cropperState.originalPdfBytes
|
||||
);
|
||||
await performMetadataCrop(pdfToModify, finalCropData);
|
||||
finalPdfBytes = await pdfToModify.save();
|
||||
}
|
||||
|
||||
const fileName = isDestructive
|
||||
? 'flattened_crop.pdf'
|
||||
: 'standard_crop.pdf';
|
||||
downloadFile(
|
||||
new Blob([finalPdfBytes], { type: 'application/pdf' }),
|
||||
fileName
|
||||
);
|
||||
showAlert('Success', 'Crop complete! Your download has started.');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showAlert('Error', 'An error occurred during cropping.');
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile } from '../utils/helpers.js';
|
||||
import { downloadFile, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { renderPagesProgressively, cleanupLazyRendering } from '../utils/render-utils.js';
|
||||
import Sortable from 'sortablejs';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
const duplicateOrganizeState = {
|
||||
sortableInstances: {},
|
||||
@@ -88,8 +91,7 @@ export async function renderDuplicateOrganizeThumbnails() {
|
||||
|
||||
showLoader('Rendering page previews...');
|
||||
const pdfData = await state.pdfDoc.save();
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdfjsDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||
const pdfjsDoc = await getPDFDocument({ data: pdfData }).promise;
|
||||
|
||||
grid.textContent = '';
|
||||
|
||||
@@ -191,7 +193,7 @@ export async function processAndSave() {
|
||||
}
|
||||
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
|
||||
|
||||
const totalPages = state.pdfDoc.getPageCount();
|
||||
const invalidIndices = finalIndices.filter(i => i >= totalPages);
|
||||
if (invalidIndices.length > 0) {
|
||||
|
||||
2040
src/js/logic/form-creator.ts
Normal file
2040
src/js/logic/form-creator.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,11 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile } from '../utils/helpers.js';
|
||||
import { downloadFile, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
|
||||
export async function invertColors() {
|
||||
if (!state.pdfDoc) {
|
||||
@@ -12,8 +16,7 @@ export async function invertColors() {
|
||||
try {
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
const pdfBytes = await state.pdfDoc.save();
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdfjsDoc = await pdfjsLib.getDocument({ data: pdfBytes }).promise;
|
||||
const pdfjsDoc = await getPDFDocument({ data: pdfBytes }).promise;
|
||||
|
||||
for (let i = 1; i <= pdfjsDoc.numPages; i++) {
|
||||
const page = await pdfjsDoc.getPage(i);
|
||||
@@ -22,7 +25,7 @@ export async function invertColors() {
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = imageData.data;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.ts';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.ts';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.ts';
|
||||
import { state } from '../state.ts';
|
||||
import { renderPagesProgressively, cleanupLazyRendering, createPlaceholder } from '../utils/render-utils.ts';
|
||||
|
||||
@@ -8,10 +8,7 @@ import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
).toString();
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
const mergeState = {
|
||||
pdfDocs: {},
|
||||
@@ -183,7 +180,7 @@ async function renderPageMergeThumbnails() {
|
||||
if (!pdfDoc) continue;
|
||||
|
||||
const pdfData = await pdfDoc.save();
|
||||
const pdfjsDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||
const pdfjsDoc = await getPDFDocument({ data: pdfData }).promise;
|
||||
|
||||
// Create a wrapper function that includes the file name
|
||||
const createWrapperWithFileName = (canvas: HTMLCanvasElement, pageNumber: number) => {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { tesseractLanguages } from '../config/tesseract-languages.js';
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import Tesseract from 'tesseract.js';
|
||||
import { PDFDocument as PDFLibDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
|
||||
let searchablePdfBytes: any = null;
|
||||
|
||||
@@ -117,8 +121,7 @@ async function runOCR() {
|
||||
tessedit_char_whitelist: whitelist,
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
@@ -136,7 +139,7 @@ async function runOCR() {
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
const context = canvas.getContext('2d');
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
await page.render({ canvasContext: context, viewport, canvas }).promise;
|
||||
|
||||
if (binarize) {
|
||||
binarizeCanvas(context);
|
||||
|
||||
@@ -1,8 +1,120 @@
|
||||
import { state } from '../state.js';
|
||||
import { getStandardPageName, convertPoints } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
|
||||
let analyzedPagesData: any = []; // Store raw data to avoid re-analyzing
|
||||
|
||||
function calculateAspectRatio(width: number, height: number): string {
|
||||
const ratio = width / height;
|
||||
return ratio.toFixed(3);
|
||||
}
|
||||
|
||||
function calculateArea(width: number, height: number, unit: string): string {
|
||||
const areaInPoints = width * height;
|
||||
let convertedArea = 0;
|
||||
let unitSuffix = '';
|
||||
|
||||
switch (unit) {
|
||||
case 'in':
|
||||
convertedArea = areaInPoints / (72 * 72); // 72 points per inch
|
||||
unitSuffix = 'in²';
|
||||
break;
|
||||
case 'mm':
|
||||
convertedArea = areaInPoints / (72 * 72) * (25.4 * 25.4); // Convert to mm²
|
||||
unitSuffix = 'mm²';
|
||||
break;
|
||||
case 'px':
|
||||
const pxPerPoint = 96 / 72;
|
||||
convertedArea = areaInPoints * (pxPerPoint * pxPerPoint);
|
||||
unitSuffix = 'px²';
|
||||
break;
|
||||
default: // 'pt'
|
||||
convertedArea = areaInPoints;
|
||||
unitSuffix = 'pt²';
|
||||
break;
|
||||
}
|
||||
|
||||
return `${convertedArea.toFixed(2)} ${unitSuffix}`;
|
||||
}
|
||||
|
||||
|
||||
function getSummaryStats() {
|
||||
const totalPages = analyzedPagesData.length;
|
||||
|
||||
// Count unique page sizes
|
||||
const uniqueSizes = new Map();
|
||||
analyzedPagesData.forEach((pageData: any) => {
|
||||
const key = `${pageData.width.toFixed(2)}x${pageData.height.toFixed(2)}`;
|
||||
const label = `${pageData.standardSize} (${pageData.orientation})`;
|
||||
uniqueSizes.set(key, {
|
||||
count: (uniqueSizes.get(key)?.count || 0) + 1,
|
||||
label: label,
|
||||
width: pageData.width,
|
||||
height: pageData.height
|
||||
});
|
||||
});
|
||||
|
||||
const hasMixedSizes = uniqueSizes.size > 1;
|
||||
|
||||
return {
|
||||
totalPages,
|
||||
uniqueSizesCount: uniqueSizes.size,
|
||||
uniqueSizes: Array.from(uniqueSizes.values()),
|
||||
hasMixedSizes
|
||||
};
|
||||
}
|
||||
|
||||
function renderSummary() {
|
||||
const summaryContainer = document.getElementById('dimensions-summary');
|
||||
if (!summaryContainer) return;
|
||||
|
||||
const stats = getSummaryStats();
|
||||
|
||||
let summaryHTML = `
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
<div class="bg-gray-900 border border-gray-700 rounded-lg p-4">
|
||||
<p class="text-sm text-gray-400 mb-1">Total Pages</p>
|
||||
<p class="text-2xl font-bold text-white">${stats.totalPages}</p>
|
||||
</div>
|
||||
<div class="bg-gray-900 border border-gray-700 rounded-lg p-4">
|
||||
<p class="text-sm text-gray-400 mb-1">Unique Page Sizes</p>
|
||||
<p class="text-2xl font-bold text-white">${stats.uniqueSizesCount}</p>
|
||||
</div>
|
||||
<div class="bg-gray-900 border border-gray-700 rounded-lg p-4">
|
||||
<p class="text-sm text-gray-400 mb-1">Document Type</p>
|
||||
<p class="text-2xl font-bold ${stats.hasMixedSizes ? 'text-yellow-400' : 'text-green-400'}">
|
||||
${stats.hasMixedSizes ? 'Mixed Sizes' : 'Uniform'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (stats.hasMixedSizes) {
|
||||
summaryHTML += `
|
||||
<div class="bg-yellow-900/20 border border-yellow-500/30 rounded-lg p-4 mb-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<i data-lucide="alert-triangle" class="w-5 h-5 text-yellow-400 mt-0.5 flex-shrink-0"></i>
|
||||
<div>
|
||||
<h4 class="text-yellow-200 font-semibold mb-2">Mixed Page Sizes Detected</h4>
|
||||
<p class="text-sm text-gray-300 mb-3">This document contains pages with different dimensions:</p>
|
||||
<ul class="space-y-1 text-sm text-gray-300">
|
||||
${stats.uniqueSizes.map((size: any) => `
|
||||
<li>• ${size.label}: ${size.count} page${size.count > 1 ? 's' : ''}</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
summaryContainer.innerHTML = summaryHTML;
|
||||
|
||||
if (stats.hasMixedSizes) {
|
||||
createIcons({ icons });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the dimensions table based on the stored data and selected unit.
|
||||
* @param {string} unit The unit to display dimensions in ('pt', 'in', 'mm', 'px').
|
||||
@@ -16,31 +128,89 @@ function renderTable(unit: any) {
|
||||
analyzedPagesData.forEach((pageData) => {
|
||||
const width = convertPoints(pageData.width, unit);
|
||||
const height = convertPoints(pageData.height, unit);
|
||||
const aspectRatio = calculateAspectRatio(pageData.width, pageData.height);
|
||||
const area = calculateArea(pageData.width, pageData.height, unit);
|
||||
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// Create and append each cell safely using textContent
|
||||
// Page number
|
||||
const pageNumCell = document.createElement('td');
|
||||
pageNumCell.className = 'px-4 py-3 text-white';
|
||||
pageNumCell.textContent = pageData.pageNum;
|
||||
|
||||
// Dimensions
|
||||
const dimensionsCell = document.createElement('td');
|
||||
dimensionsCell.className = 'px-4 py-3 text-gray-300';
|
||||
dimensionsCell.textContent = `${width} x ${height} ${unit}`;
|
||||
|
||||
// Standard size
|
||||
const sizeCell = document.createElement('td');
|
||||
sizeCell.className = 'px-4 py-3 text-gray-300';
|
||||
sizeCell.textContent = pageData.standardSize;
|
||||
|
||||
// Orientation
|
||||
const orientationCell = document.createElement('td');
|
||||
orientationCell.className = 'px-4 py-3 text-gray-300';
|
||||
orientationCell.textContent = pageData.orientation;
|
||||
|
||||
row.append(pageNumCell, dimensionsCell, sizeCell, orientationCell);
|
||||
// Aspect Ratio
|
||||
const aspectRatioCell = document.createElement('td');
|
||||
aspectRatioCell.className = 'px-4 py-3 text-gray-300';
|
||||
aspectRatioCell.textContent = aspectRatio;
|
||||
|
||||
// Area
|
||||
const areaCell = document.createElement('td');
|
||||
areaCell.className = 'px-4 py-3 text-gray-300';
|
||||
areaCell.textContent = area;
|
||||
|
||||
// Rotation
|
||||
const rotationCell = document.createElement('td');
|
||||
rotationCell.className = 'px-4 py-3 text-gray-300';
|
||||
rotationCell.textContent = `${pageData.rotation}°`;
|
||||
|
||||
row.append(pageNumCell, dimensionsCell, sizeCell, orientationCell, aspectRatioCell, areaCell, rotationCell);
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function exportToCSV() {
|
||||
const unitsSelect = document.getElementById('units-select') as HTMLSelectElement;
|
||||
const unit = unitsSelect?.value || 'pt';
|
||||
|
||||
const headers = ['Page #', `Width (${unit})`, `Height (${unit})`, 'Standard Size', 'Orientation', 'Aspect Ratio', `Area (${unit}²)`, 'Rotation'];
|
||||
const csvRows = [headers.join(',')];
|
||||
|
||||
analyzedPagesData.forEach((pageData: any) => {
|
||||
const width = convertPoints(pageData.width, unit);
|
||||
const height = convertPoints(pageData.height, unit);
|
||||
const aspectRatio = calculateAspectRatio(pageData.width, pageData.height);
|
||||
const area = calculateArea(pageData.width, pageData.height, unit);
|
||||
|
||||
const row = [
|
||||
pageData.pageNum,
|
||||
width,
|
||||
height,
|
||||
pageData.standardSize,
|
||||
pageData.orientation,
|
||||
aspectRatio,
|
||||
area,
|
||||
`${pageData.rotation}°`
|
||||
];
|
||||
csvRows.push(row.join(','));
|
||||
});
|
||||
|
||||
const csvContent = csvRows.join('\n');
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'page-dimensions.csv';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to analyze the PDF and display dimensions.
|
||||
* This is called once after the file is loaded.
|
||||
@@ -53,28 +223,36 @@ export function analyzeAndDisplayDimensions() {
|
||||
|
||||
pages.forEach((page: any, index: any) => {
|
||||
const { width, height } = page.getSize();
|
||||
const rotation = page.getRotation().angle || 0;
|
||||
|
||||
analyzedPagesData.push({
|
||||
pageNum: index + 1,
|
||||
width, // Store raw width in points
|
||||
height, // Store raw height in points
|
||||
orientation: width > height ? 'Landscape' : 'Portrait',
|
||||
standardSize: getStandardPageName(width, height),
|
||||
rotation: rotation
|
||||
});
|
||||
});
|
||||
|
||||
const resultsContainer = document.getElementById('dimensions-results');
|
||||
const unitsSelect = document.getElementById('units-select');
|
||||
|
||||
renderSummary();
|
||||
|
||||
// Initial render with default unit (points)
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
renderTable(unitsSelect.value);
|
||||
|
||||
// Show the results table
|
||||
resultsContainer.classList.remove('hidden');
|
||||
|
||||
// Add event listener to handle unit changes
|
||||
unitsSelect.addEventListener('change', (e) => {
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'EventTarg... Remove this comment to see the full error message
|
||||
renderTable(e.target.value);
|
||||
});
|
||||
|
||||
const exportButton = document.getElementById('export-csv-btn');
|
||||
if (exportButton) {
|
||||
exportButton.addEventListener('click', exportToCSV);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { degrees, PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import JSZip from 'jszip';
|
||||
import Sortable from 'sortablejs';
|
||||
import { downloadFile } from '../utils/helpers';
|
||||
import { downloadFile, getPDFDocument } from '../utils/helpers';
|
||||
import { renderPagesProgressively, cleanupLazyRendering, renderPageToCanvas, createPlaceholder } from '../utils/render-utils';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
|
||||
@@ -357,7 +357,7 @@ async function loadPdfs(files: File[]) {
|
||||
const pdfIndex = currentPdfDocs.length - 1;
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
const pdfjsDoc = await pdfjsLib.getDocument({ data: new Uint8Array(pdfBytes) }).promise;
|
||||
const pdfjsDoc = await getPDFDocument({ data: new Uint8Array(pdfBytes) }).promise;
|
||||
const numPages = pdfjsDoc.numPages;
|
||||
|
||||
// Pre-fill allPages with placeholders to maintain order/state
|
||||
@@ -741,7 +741,7 @@ async function handleInsertPdf(e: Event) {
|
||||
|
||||
// Load PDF.js document for rendering
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
const pdfjsDoc = await pdfjsLib.getDocument({ data: new Uint8Array(pdfBytes) }).promise;
|
||||
const pdfjsDoc = await getPDFDocument({ data: new Uint8Array(pdfBytes) }).promise;
|
||||
const numPages = pdfjsDoc.numPages;
|
||||
|
||||
const newPages: PageData[] = [];
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import JSZip from 'jszip';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
/**
|
||||
* Creates a BMP file buffer from raw pixel data (ImageData).
|
||||
@@ -53,8 +56,7 @@ function encodeBMP(imageData: any) {
|
||||
export async function pdfToBmp() {
|
||||
showLoader('Converting PDF to BMP images...');
|
||||
try {
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const zip = new JSZip();
|
||||
@@ -69,7 +71,7 @@ export async function pdfToBmp() {
|
||||
canvas.width = viewport.width;
|
||||
|
||||
// Render the PDF page directly to the canvas
|
||||
await page.render({ canvasContext: context, viewport: viewport }).promise;
|
||||
await page.render({ canvasContext: context, viewport: viewport, canvas }).promise;
|
||||
|
||||
// Get the raw pixel data from this canvas
|
||||
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile } from '../utils/helpers.js';
|
||||
import { downloadFile, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
export async function pdfToGreyscale() {
|
||||
if (!state.pdfDoc) {
|
||||
showAlert('Error', 'PDF not loaded.');
|
||||
@@ -13,8 +15,7 @@ export async function pdfToGreyscale() {
|
||||
try {
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
const pdfBytes = await state.pdfDoc.save();
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdfjsDoc = await pdfjsLib.getDocument({ data: pdfBytes }).promise;
|
||||
const pdfjsDoc = await getPDFDocument({ data: pdfBytes }).promise;
|
||||
|
||||
for (let i = 1; i <= pdfjsDoc.numPages; i++) {
|
||||
const page = await pdfjsDoc.getPage(i);
|
||||
@@ -24,7 +25,7 @@ export async function pdfToGreyscale() {
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = imageData.data;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import JSZip from 'jszip';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
|
||||
export async function pdfToJpg() {
|
||||
showLoader('Converting to JPG...');
|
||||
try {
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const zip = new JSZip();
|
||||
@@ -23,7 +26,7 @@ export async function pdfToJpg() {
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
await page.render({ canvasContext: context, viewport: viewport }).promise;
|
||||
await page.render({ canvasContext: context, viewport: viewport, canvas }).promise;
|
||||
|
||||
const blob = await new Promise((resolve) =>
|
||||
canvas.toBlob(resolve, 'image/jpeg', quality)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
export async function pdfToMarkdown() {
|
||||
@@ -7,8 +7,7 @@ export async function pdfToMarkdown() {
|
||||
try {
|
||||
const file = state.files[0];
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||||
const pdf = await getPDFDocument({ data: arrayBuffer }).promise;
|
||||
let markdown = '';
|
||||
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import JSZip from 'jszip';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
|
||||
export async function pdfToPng() {
|
||||
showLoader('Converting to PNG...');
|
||||
try {
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const zip = new JSZip();
|
||||
|
||||
|
||||
const qualityInput = document.getElementById('png-quality') as HTMLInputElement;
|
||||
const scale = qualityInput ? parseFloat(qualityInput.value) : 2.0;
|
||||
|
||||
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i);
|
||||
const viewport = page.getViewport({ scale });
|
||||
@@ -22,7 +25,7 @@ export async function pdfToPng() {
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
const context = canvas.getContext('2d');
|
||||
await page.render({ canvasContext: context, viewport: viewport }).promise;
|
||||
await page.render({ canvasContext: context, viewport: viewport, canvas }).promise;
|
||||
const blob = await new Promise((resolve) =>
|
||||
canvas.toBlob(resolve, 'image/png')
|
||||
);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import JSZip from 'jszip';
|
||||
import UTIF from 'utif';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
export async function pdfToTiff() {
|
||||
showLoader('Converting PDF to TIFF...');
|
||||
try {
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const zip = new JSZip();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import JSZip from 'jszip';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
|
||||
export async function pdfToWebp() {
|
||||
showLoader('Converting to WebP...');
|
||||
try {
|
||||
// @ts-expect-error TS(2304) FIXME: Cannot find name 'pdfjsLib'.
|
||||
const pdf = await pdfjsLib.getDocument(
|
||||
const pdf = await getPDFDocument(
|
||||
await readFileAsArrayBuffer(state.files[0])
|
||||
).promise;
|
||||
const zip = new JSZip();
|
||||
@@ -18,10 +21,10 @@ export async function pdfToWebp() {
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
const context = canvas.getContext('2d');
|
||||
await page.render({ canvasContext: context, viewport: viewport }).promise;
|
||||
await page.render({ canvasContext: context, viewport: viewport, canvas }).promise;
|
||||
const qualityInput = document.getElementById('webp-quality') as HTMLInputElement;
|
||||
const quality = qualityInput ? parseFloat(qualityInput.value) : 0.9;
|
||||
|
||||
|
||||
const blob = await new Promise((resolve) =>
|
||||
canvas.toBlob(resolve, 'image/webp', quality)
|
||||
);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, parsePageRanges } from '../utils/helpers.js';
|
||||
import { downloadFile, parsePageRanges, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { PDFDocument, PageSizes } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
const posterizeState = {
|
||||
pdfJsDoc: null,
|
||||
pageSnapshots: {},
|
||||
@@ -121,7 +123,7 @@ export async function setupPosterizeTool() {
|
||||
.getPageCount()
|
||||
.toString();
|
||||
const pdfBytes = await state.pdfDoc.save();
|
||||
posterizeState.pdfJsDoc = await pdfjsLib.getDocument({ data: pdfBytes })
|
||||
posterizeState.pdfJsDoc = await getPDFDocument({ data: pdfBytes })
|
||||
.promise;
|
||||
posterizeState.pageSnapshots = {};
|
||||
posterizeState.currentPage = 1;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile } from '../utils/helpers.js';
|
||||
import { downloadFile, getPDFDocument } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { PDFPageProxy } from 'pdfjs-dist/types/src/display/api.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
let analysisCache = [];
|
||||
|
||||
async function isPageBlank(page: PDFPageProxy, threshold: number) {
|
||||
@@ -38,7 +40,7 @@ async function analyzePages() {
|
||||
showLoader('Analyzing for blank pages...');
|
||||
|
||||
const pdfBytes = await state.pdfDoc.save();
|
||||
const pdf = await pdfjsLib.getDocument({ data: pdfBytes }).promise;
|
||||
const pdf = await getPDFDocument({ data: pdfBytes }).promise;
|
||||
|
||||
analysisCache = [];
|
||||
const promises = [];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile } from '../utils/helpers.js';
|
||||
import { downloadFile, resetAndReloadTool } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import { getRotationState } from '../handlers/fileHandler.js';
|
||||
import { getRotationState, resetRotationState } from '../handlers/fileHandler.js';
|
||||
|
||||
import { degrees } from 'pdf-lib';
|
||||
|
||||
@@ -24,6 +24,10 @@ export async function rotate() {
|
||||
new Blob([rotatedPdfBytes], { type: 'application/pdf' }),
|
||||
'rotated.pdf'
|
||||
);
|
||||
|
||||
resetAndReloadTool(() => {
|
||||
resetRotationState();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showAlert('Error', 'Could not apply rotations.');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
@@ -50,7 +51,7 @@ export async function setupSignTool() {
|
||||
enablePermissions: false,
|
||||
};
|
||||
localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs));
|
||||
} catch {}
|
||||
} catch { }
|
||||
|
||||
const viewerUrl = new URL('/pdfjs-viewer/viewer.html', window.location.origin);
|
||||
const query = new URLSearchParams({ file: blobUrl });
|
||||
@@ -83,7 +84,7 @@ export async function setupSignTool() {
|
||||
try {
|
||||
const highlightBtn = doc.getElementById('editorHighlightButton') as HTMLButtonElement | null;
|
||||
highlightBtn?.click();
|
||||
} catch {}
|
||||
} catch { }
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -115,11 +116,41 @@ export async function applyAndSaveSignatures() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delegate to the built-in download behavior of the base viewer.
|
||||
const app = viewerWindow.PDFViewerApplication;
|
||||
app.eventBus?.dispatch('download', { source: app });
|
||||
const flattenCheckbox = document.getElementById('flatten-signature-toggle') as HTMLInputElement | null;
|
||||
const shouldFlatten = flattenCheckbox?.checked;
|
||||
|
||||
if (shouldFlatten) {
|
||||
showLoader('Flattening and saving PDF...');
|
||||
|
||||
const rawPdfBytes = await app.pdfDocument.saveDocument(app.pdfDocument.annotationStorage);
|
||||
|
||||
const pdfBytes = new Uint8Array(rawPdfBytes);
|
||||
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
|
||||
pdfDoc.getForm().flatten();
|
||||
|
||||
const flattenedPdfBytes = await pdfDoc.save();
|
||||
|
||||
const blob = new Blob([flattenedPdfBytes as BlobPart], { type: 'application/pdf' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `signed_flattened_${state.files[0].name}`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
hideLoader();
|
||||
} else {
|
||||
// Delegate to the built-in download behavior of the base viewer.
|
||||
app.eventBus?.dispatch('download', { source: app });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to trigger download in base PDF.js viewer:', error);
|
||||
console.error('Failed to export the signed PDF:', error);
|
||||
hideLoader();
|
||||
showAlert('Export failed', 'Could not export the signed PDF. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { downloadFile } from '../utils/helpers.js';
|
||||
import { downloadFile, getPDFDocument } from '../utils/helpers.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
import { state } from '../state.js';
|
||||
import { renderPagesProgressively, cleanupLazyRendering } from '../utils/render-utils.js';
|
||||
import JSZip from 'jszip';
|
||||
@@ -27,7 +29,7 @@ async function renderVisualSelector() {
|
||||
|
||||
try {
|
||||
const pdfData = await state.pdfDoc.save();
|
||||
const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||
const pdf = await getPDFDocument({ data: pdfData }).promise;
|
||||
|
||||
// Function to create wrapper element for each page
|
||||
const createWrapper = (canvas: HTMLCanvasElement, pageNumber: number) => {
|
||||
|
||||
Reference in New Issue
Block a user