mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-08 08:52:28 +01:00
Refactor and enhance type safety across various modules
- Updated function parameters and return types in `page-preview.ts`, `pdf-decrypt.ts`, and `pymupdf-loader.ts` for improved type safety. - Introduced type definitions for `CpdfInstance`, `PyMuPDFInstance`, and other related types to ensure better type checking. - Enhanced error handling in `sanitize.ts` by creating a utility function for error messages. - Removed unnecessary type assertions and improved type inference in `editor.ts`, `serialization.ts`, and `tools.test.ts`. - Added type definitions for markdown-it plugins to improve compatibility and type safety. - Enforced stricter TypeScript settings by enabling `noImplicitAny` in `tsconfig.json`. - Cleaned up test files by refining type assertions and ensuring consistency in type usage.
This commit is contained in:
1 parent
a1fc2fc3c6
commit
9d0b68e18c
114 files changed
+2576
-1867
No files matched your search
+103
-88
@@ -4,25 +4,34 @@ import { state } from './state.js';
|
||||
import { toolLogic } from './logic/index.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';
|
||||
import type { CropBox } from '@/types';
|
||||
|
||||
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 editorState: {
|
||||
pdf: any;
|
||||
canvas: any;
|
||||
context: any;
|
||||
container: any;
|
||||
pdf: PDFDocumentProxy | null;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
context: CanvasRenderingContext2D | null;
|
||||
container: HTMLElement | null;
|
||||
currentPageNum: number;
|
||||
pageRendering: boolean;
|
||||
pageNumPending: number | null;
|
||||
scale: number | 'fit';
|
||||
pageSnapshot: any;
|
||||
pageSnapshot: ImageData | null;
|
||||
isDrawing: boolean;
|
||||
startX: number;
|
||||
startY: number;
|
||||
cropBoxes: Record<number, any>;
|
||||
lastInteractionRect: { x: number; y: number; width: number; height: number } | null;
|
||||
cropBoxes: Record<number, CropBox>;
|
||||
lastInteractionRect: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} | null;
|
||||
} = {
|
||||
pdf: null,
|
||||
canvas: null,
|
||||
@@ -37,15 +46,15 @@ const editorState: {
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
cropBoxes: {},
|
||||
lastInteractionRect: null, // Used to store the rectangle from the last move event
|
||||
lastInteractionRect: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the best scale to fit the page within the container.
|
||||
* @param {PDFPageProxy} page - The PDF.js page object.
|
||||
*/
|
||||
function calculateFitScale(page: any) {
|
||||
const containerWidth = editorState.container.clientWidth;
|
||||
function calculateFitScale(page: PDFPageProxy) {
|
||||
const containerWidth = editorState.container!.clientWidth;
|
||||
const viewport = page.getViewport({ scale: 1.0 });
|
||||
return containerWidth / viewport.width;
|
||||
}
|
||||
@@ -54,33 +63,34 @@ function calculateFitScale(page: any) {
|
||||
* Renders a specific page of the PDF onto the canvas.
|
||||
* @param {number} num The page number to render.
|
||||
*/
|
||||
async function renderPage(num: any) {
|
||||
async function renderPage(num: number) {
|
||||
editorState.pageRendering = true;
|
||||
showLoader(`Loading page ${num}...`);
|
||||
|
||||
try {
|
||||
const page = await editorState.pdf.getPage(num);
|
||||
const page = await editorState.pdf!.getPage(num);
|
||||
|
||||
if (editorState.scale === 'fit') {
|
||||
editorState.scale = calculateFitScale(page);
|
||||
}
|
||||
|
||||
const viewport = page.getViewport({ scale: editorState.scale });
|
||||
editorState.canvas.height = viewport.height;
|
||||
editorState.canvas.width = viewport.width;
|
||||
const viewport = page.getViewport({ scale: editorState.scale as number });
|
||||
editorState.canvas!.height = viewport.height;
|
||||
editorState.canvas!.width = viewport.width;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: editorState.context,
|
||||
canvas: editorState.canvas,
|
||||
canvasContext: editorState.context!,
|
||||
viewport: viewport,
|
||||
};
|
||||
|
||||
await page.render(renderContext).promise;
|
||||
|
||||
editorState.pageSnapshot = editorState.context.getImageData(
|
||||
editorState.pageSnapshot = editorState.context!.getImageData(
|
||||
0,
|
||||
0,
|
||||
editorState.canvas.width,
|
||||
editorState.canvas.height
|
||||
editorState.canvas!.width,
|
||||
editorState.canvas!.height
|
||||
);
|
||||
redrawShapes();
|
||||
} catch (error) {
|
||||
@@ -90,12 +100,11 @@ async function renderPage(num: any) {
|
||||
editorState.pageRendering = false;
|
||||
hideLoader();
|
||||
|
||||
document.getElementById('current-page-display').textContent = num;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'disabled' does not exist on type 'HTMLEl... Remove this comment to see the full error message
|
||||
document.getElementById('prev-page').disabled = num <= 1;
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'disabled' does not exist on type 'HTMLEl... Remove this comment to see the full error message
|
||||
document.getElementById('next-page').disabled =
|
||||
num >= editorState.pdf.numPages;
|
||||
document.getElementById('current-page-display')!.textContent = String(num);
|
||||
(document.getElementById('prev-page') as HTMLButtonElement).disabled =
|
||||
num <= 1;
|
||||
(document.getElementById('next-page') as HTMLButtonElement).disabled =
|
||||
num >= editorState.pdf!.numPages;
|
||||
|
||||
if (editorState.pageNumPending !== null) {
|
||||
const pendingPage = editorState.pageNumPending;
|
||||
@@ -105,7 +114,7 @@ async function renderPage(num: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function queueRenderPage(num: any) {
|
||||
function queueRenderPage(num: number) {
|
||||
if (editorState.pageRendering) {
|
||||
editorState.pageNumPending = num;
|
||||
} else {
|
||||
@@ -116,36 +125,37 @@ function queueRenderPage(num: any) {
|
||||
|
||||
function redrawShapes() {
|
||||
if (editorState.pageSnapshot) {
|
||||
editorState.context.putImageData(editorState.pageSnapshot, 0, 0);
|
||||
editorState.context!.putImageData(editorState.pageSnapshot, 0, 0);
|
||||
}
|
||||
|
||||
const currentCropBox = editorState.cropBoxes[editorState.currentPageNum - 1];
|
||||
if (currentCropBox) {
|
||||
editorState.context.strokeStyle = 'rgba(79, 70, 229, 0.9)';
|
||||
editorState.context.lineWidth = 2;
|
||||
editorState.context.setLineDash([8, 4]);
|
||||
editorState.context.strokeRect(
|
||||
editorState.context!.strokeStyle = 'rgba(79, 70, 229, 0.9)';
|
||||
editorState.context!.lineWidth = 2;
|
||||
editorState.context!.setLineDash([8, 4]);
|
||||
editorState.context!.strokeRect(
|
||||
currentCropBox.x,
|
||||
currentCropBox.y,
|
||||
currentCropBox.width,
|
||||
currentCropBox.height
|
||||
);
|
||||
editorState.context.setLineDash([]);
|
||||
editorState.context!.setLineDash([]);
|
||||
}
|
||||
}
|
||||
|
||||
function getEventCoordinates(e: any) {
|
||||
const rect = editorState.canvas.getBoundingClientRect();
|
||||
const touch = e.touches ? e.touches[0] : e;
|
||||
const scaleX = editorState.canvas.width / rect.width;
|
||||
const scaleY = editorState.canvas.height / rect.height;
|
||||
function getEventCoordinates(e: MouseEvent | TouchEvent) {
|
||||
const rect = editorState.canvas!.getBoundingClientRect();
|
||||
const touch =
|
||||
'touches' in e && e.touches.length > 0 ? e.touches[0] : (e as MouseEvent);
|
||||
const scaleX = editorState.canvas!.width / rect.width;
|
||||
const scaleY = editorState.canvas!.height / rect.height;
|
||||
return {
|
||||
x: (touch.clientX - rect.left) * scaleX,
|
||||
y: (touch.clientY - rect.top) * scaleY,
|
||||
};
|
||||
}
|
||||
|
||||
function handleInteractionStart(e: any) {
|
||||
function handleInteractionStart(e: MouseEvent | TouchEvent) {
|
||||
e.preventDefault();
|
||||
const coords = getEventCoordinates(e);
|
||||
editorState.isDrawing = true;
|
||||
@@ -153,7 +163,7 @@ function handleInteractionStart(e: any) {
|
||||
editorState.startY = coords.y;
|
||||
}
|
||||
|
||||
function handleInteractionMove(e: any) {
|
||||
function handleInteractionMove(e: MouseEvent | TouchEvent) {
|
||||
if (!editorState.isDrawing) return;
|
||||
e.preventDefault();
|
||||
|
||||
@@ -165,13 +175,12 @@ function handleInteractionMove(e: any) {
|
||||
const width = Math.abs(editorState.startX - coords.x);
|
||||
const height = Math.abs(editorState.startY - coords.y);
|
||||
|
||||
editorState.context.strokeStyle = 'rgba(79, 70, 229, 0.9)';
|
||||
editorState.context.lineWidth = 2;
|
||||
editorState.context.setLineDash([8, 4]);
|
||||
editorState.context.strokeRect(x, y, width, height);
|
||||
editorState.context.setLineDash([]);
|
||||
editorState.context!.strokeStyle = 'rgba(79, 70, 229, 0.9)';
|
||||
editorState.context!.lineWidth = 2;
|
||||
editorState.context!.setLineDash([8, 4]);
|
||||
editorState.context!.strokeRect(x, y, width, height);
|
||||
editorState.context!.setLineDash([]);
|
||||
|
||||
// Store the last valid rectangle drawn during the move event
|
||||
editorState.lastInteractionRect = { x, y, width, height };
|
||||
}
|
||||
|
||||
@@ -192,12 +201,14 @@ function handleInteractionEnd() {
|
||||
scale: editorState.scale,
|
||||
};
|
||||
|
||||
editorState.lastInteractionRect = null; // Reset for the next drawing action
|
||||
editorState.lastInteractionRect = null;
|
||||
redrawShapes();
|
||||
}
|
||||
|
||||
export async function setupCanvasEditor(toolId: any) {
|
||||
editorState.canvas = document.getElementById('canvas-editor');
|
||||
export async function setupCanvasEditor(toolId: string) {
|
||||
editorState.canvas = document.getElementById(
|
||||
'canvas-editor'
|
||||
) as HTMLCanvasElement | null;
|
||||
if (!editorState.canvas) return;
|
||||
editorState.container = document.getElementById('canvas-container');
|
||||
editorState.context = editorState.canvas.getContext('2d');
|
||||
@@ -210,7 +221,7 @@ export async function setupCanvasEditor(toolId: any) {
|
||||
editorState.currentPageNum = 1;
|
||||
editorState.scale = 'fit';
|
||||
|
||||
pageNav.textContent = '';
|
||||
pageNav!.textContent = '';
|
||||
|
||||
const prevButton = document.createElement('button');
|
||||
prevButton.id = 'prev-page';
|
||||
@@ -237,22 +248,21 @@ export async function setupCanvasEditor(toolId: any) {
|
||||
'btn p-2 rounded-full bg-gray-700 hover:bg-gray-600 disabled:opacity-50';
|
||||
nextButton.innerHTML = '<i data-lucide="chevron-right"></i>';
|
||||
|
||||
pageNav.append(prevButton, pageInfo, nextButton);
|
||||
pageNav!.append(prevButton, pageInfo, nextButton);
|
||||
|
||||
createIcons({ icons });
|
||||
|
||||
document.getElementById('prev-page').addEventListener('click', () => {
|
||||
document.getElementById('prev-page')!.addEventListener('click', () => {
|
||||
if (editorState.currentPageNum > 1)
|
||||
queueRenderPage(editorState.currentPageNum - 1);
|
||||
});
|
||||
document.getElementById('next-page').addEventListener('click', () => {
|
||||
if (editorState.currentPageNum < editorState.pdf.numPages)
|
||||
document.getElementById('next-page')!.addEventListener('click', () => {
|
||||
if (editorState.currentPageNum < editorState.pdf!.numPages)
|
||||
queueRenderPage(editorState.currentPageNum + 1);
|
||||
});
|
||||
|
||||
// To prevent stacking multiple listeners, we replace the canvas element with a clone
|
||||
const newCanvas = editorState.canvas.cloneNode(true);
|
||||
editorState.canvas.parentNode.replaceChild(newCanvas, editorState.canvas);
|
||||
const newCanvas = editorState.canvas.cloneNode(true) as HTMLCanvasElement;
|
||||
editorState.canvas.parentNode!.replaceChild(newCanvas, editorState.canvas);
|
||||
editorState.canvas = newCanvas;
|
||||
editorState.context = newCanvas.getContext('2d');
|
||||
|
||||
@@ -272,50 +282,55 @@ export async function setupCanvasEditor(toolId: any) {
|
||||
editorState.canvas.addEventListener('touchend', handleInteractionEnd);
|
||||
|
||||
if (toolId === 'crop') {
|
||||
document.getElementById('zoom-in-btn').onclick = () => {
|
||||
(document.getElementById('zoom-in-btn') as HTMLElement).onclick = () => {
|
||||
if (typeof editorState.scale === 'number') {
|
||||
editorState.scale += 0.25;
|
||||
}
|
||||
renderPage(editorState.currentPageNum);
|
||||
};
|
||||
document.getElementById('zoom-out-btn').onclick = () => {
|
||||
(document.getElementById('zoom-out-btn') as HTMLElement).onclick = () => {
|
||||
if (typeof editorState.scale === 'number' && editorState.scale > 0.25) {
|
||||
editorState.scale -= 0.25;
|
||||
renderPage(editorState.currentPageNum);
|
||||
}
|
||||
};
|
||||
document.getElementById('fit-page-btn').onclick = async () => {
|
||||
const page = await editorState.pdf.getPage(editorState.currentPageNum);
|
||||
editorState.scale = calculateFitScale(page);
|
||||
renderPage(editorState.currentPageNum);
|
||||
};
|
||||
document.getElementById('clear-crop-btn').onclick = () => {
|
||||
(document.getElementById('fit-page-btn') as HTMLElement).onclick =
|
||||
async () => {
|
||||
const page = await editorState.pdf!.getPage(editorState.currentPageNum);
|
||||
editorState.scale = calculateFitScale(page);
|
||||
renderPage(editorState.currentPageNum);
|
||||
};
|
||||
(document.getElementById('clear-crop-btn') as HTMLElement).onclick = () => {
|
||||
delete editorState.cropBoxes[editorState.currentPageNum - 1];
|
||||
redrawShapes();
|
||||
};
|
||||
document.getElementById('clear-all-crops-btn').onclick = () => {
|
||||
editorState.cropBoxes = {};
|
||||
redrawShapes();
|
||||
};
|
||||
(document.getElementById('clear-all-crops-btn') as HTMLElement).onclick =
|
||||
() => {
|
||||
editorState.cropBoxes = {};
|
||||
redrawShapes();
|
||||
};
|
||||
|
||||
document.getElementById('process-btn').onclick = async () => {
|
||||
if (Object.keys(editorState.cropBoxes).length === 0) {
|
||||
showAlert(
|
||||
'No Area Selected',
|
||||
'Please draw a rectangle on at least one page to select the crop area.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const success = await toolLogic['crop-pdf'].process(
|
||||
editorState.cropBoxes
|
||||
);
|
||||
if (success) {
|
||||
showAlert(
|
||||
'Success!',
|
||||
'Your PDF has been cropped and the download has started.'
|
||||
);
|
||||
}
|
||||
};
|
||||
(document.getElementById('process-btn') as HTMLElement).onclick =
|
||||
async () => {
|
||||
if (Object.keys(editorState.cropBoxes).length === 0) {
|
||||
showAlert(
|
||||
'No Area Selected',
|
||||
'Please draw a rectangle on at least one page to select the crop area.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const cropLogic = toolLogic['crop-pdf'];
|
||||
const success =
|
||||
typeof cropLogic === 'object' && cropLogic.process
|
||||
? await cropLogic.process(editorState.cropBoxes)
|
||||
: false;
|
||||
if (success) {
|
||||
showAlert(
|
||||
'Success!',
|
||||
'Your PDF has been cropped and the download has started.'
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
queueRenderPage(1);
|
||||
|
||||
Reference in new issue
Block a user