mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-09-27 20:24:50 +02: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:
@@ -69,7 +69,10 @@ export async function performCondenseCompression(
|
||||
scrub: {
|
||||
metadata: customSettings?.removeMetadata ?? preset.scrub.metadata,
|
||||
thumbnails: customSettings?.removeThumbnails ?? preset.scrub.thumbnails,
|
||||
xmlMetadata: (preset.scrub as any).xmlMetadata ?? false,
|
||||
xmlMetadata:
|
||||
('xmlMetadata' in preset.scrub
|
||||
? (preset.scrub as { xmlMetadata?: boolean }).xmlMetadata
|
||||
: undefined) ?? false,
|
||||
},
|
||||
subsetFonts: customSettings?.subsetFonts ?? preset.subsetFonts,
|
||||
save: {
|
||||
@@ -95,8 +98,11 @@ export async function performCondenseCompression(
|
||||
try {
|
||||
const result = await pymupdf.compressPdf(fileBlob, fallbackOptions);
|
||||
return { ...result, usedFallback: true };
|
||||
} catch (fallbackError: any) {
|
||||
const msg = fallbackError?.message || String(fallbackError);
|
||||
} catch (fallbackError: unknown) {
|
||||
const msg =
|
||||
fallbackError instanceof Error
|
||||
? fallbackError.message
|
||||
: String(fallbackError);
|
||||
throw new Error(`PDF compression failed: ${msg}`, {
|
||||
cause: fallbackError,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { WasmProvider } from './wasm-provider';
|
||||
import type { WindowWithCoherentPdf, CpdfInstance } from '@/types';
|
||||
|
||||
let cpdfLoaded = false;
|
||||
let cpdfLoadPromise: Promise<void> | null = null;
|
||||
@@ -31,7 +32,7 @@ export async function isCpdfLoaded(): Promise<void> {
|
||||
}
|
||||
|
||||
cpdfLoadPromise = new Promise((resolve, reject) => {
|
||||
if (typeof (window as any).coherentpdf !== 'undefined') {
|
||||
if (typeof (window as WindowWithCoherentPdf).coherentpdf !== 'undefined') {
|
||||
cpdfLoaded = true;
|
||||
resolve();
|
||||
return;
|
||||
@@ -53,7 +54,7 @@ export async function isCpdfLoaded(): Promise<void> {
|
||||
return cpdfLoadPromise;
|
||||
}
|
||||
|
||||
export async function getCpdf(): Promise<any> {
|
||||
export async function getCpdf(): Promise<CpdfInstance> {
|
||||
await isCpdfLoaded();
|
||||
return (window as any).coherentpdf;
|
||||
return (window as WindowWithCoherentPdf).coherentpdf as CpdfInstance;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ export async function loadRuntimeConfig(): Promise<void> {
|
||||
(c): c is string => typeof c === 'string'
|
||||
);
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
console.error('[LOAD_RUNTIME_CONFIG] Failed to load runtime configuration');
|
||||
}
|
||||
}
|
||||
|
||||
export function isToolDisabled(toolId: string): boolean {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { WasmProvider } from './wasm-provider.js';
|
||||
import type {
|
||||
GlobalScopeWithGhostscript,
|
||||
GhostscriptDynamicInstance,
|
||||
} from '@/types';
|
||||
|
||||
let cachedGS: any = null;
|
||||
let loadPromise: Promise<any> | null = null;
|
||||
let cachedGS: GhostscriptInterface | null = null;
|
||||
let loadPromise: Promise<GhostscriptInterface> | null = null;
|
||||
|
||||
export interface GhostscriptInterface {
|
||||
convertToPDFA(pdfBuffer: ArrayBuffer, profile: string): Promise<ArrayBuffer>;
|
||||
@@ -32,16 +36,20 @@ export async function loadGhostscript(): Promise<GhostscriptInterface> {
|
||||
|
||||
await loadScript(wrapperUrl);
|
||||
|
||||
const globalScope =
|
||||
typeof globalThis !== 'undefined' ? globalThis : window;
|
||||
const globalScope = (
|
||||
typeof globalThis !== 'undefined' ? globalThis : window
|
||||
) as typeof globalThis & GlobalScopeWithGhostscript;
|
||||
|
||||
if (typeof (globalScope as any).loadGS === 'function') {
|
||||
cachedGS = await (globalScope as any).loadGS({
|
||||
if (typeof globalScope.loadGS === 'function') {
|
||||
const instance = await globalScope.loadGS({
|
||||
baseUrl: normalizedUrl,
|
||||
});
|
||||
} else if (typeof (globalScope as any).GhostscriptWASM === 'function') {
|
||||
cachedGS = new (globalScope as any).GhostscriptWASM(normalizedUrl);
|
||||
await cachedGS.init?.();
|
||||
cachedGS = instance as unknown as GhostscriptInterface;
|
||||
} else if (typeof globalScope.GhostscriptWASM === 'function') {
|
||||
const instance: GhostscriptDynamicInstance =
|
||||
new globalScope.GhostscriptWASM(normalizedUrl);
|
||||
await instance.init?.();
|
||||
cachedGS = instance as unknown as GhostscriptInterface;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Ghostscript wrapper did not expose expected interface. Expected loadGS() or GhostscriptWASM class.'
|
||||
@@ -49,10 +57,11 @@ export async function loadGhostscript(): Promise<GhostscriptInterface> {
|
||||
}
|
||||
|
||||
return cachedGS;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
loadPromise = null;
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(
|
||||
`Failed to load Ghostscript from ${normalizedUrl}: ${error.message}`,
|
||||
`Failed to load Ghostscript from ${normalizedUrl}: ${msg}`,
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
|
||||
+23
-24
@@ -1,8 +1,10 @@
|
||||
import createModule from '@neslinesli93/qpdf-wasm';
|
||||
import type { QpdfInstanceExtended } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { createIcons } from 'lucide';
|
||||
import { state, resetState } from '../state.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import type { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
const STANDARD_SIZES = {
|
||||
A4: { width: 595.28, height: 841.89 },
|
||||
@@ -13,7 +15,7 @@ const STANDARD_SIZES = {
|
||||
A5: { width: 419.53, height: 595.28 },
|
||||
};
|
||||
|
||||
export function getStandardPageName(width: any, height: any) {
|
||||
export function getStandardPageName(width: number, height: number) {
|
||||
const tolerance = 1; // Allow for minor floating point variations
|
||||
for (const [name, size] of Object.entries(STANDARD_SIZES)) {
|
||||
if (
|
||||
@@ -28,7 +30,7 @@ export function getStandardPageName(width: any, height: any) {
|
||||
return 'Custom';
|
||||
}
|
||||
|
||||
export function convertPoints(points: any, unit: any) {
|
||||
export function convertPoints(points: number, unit: string) {
|
||||
let result: number;
|
||||
switch (unit) {
|
||||
case 'in':
|
||||
@@ -59,7 +61,7 @@ export function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
||||
: { r: 0, g: 0, b: 0 };
|
||||
}
|
||||
|
||||
export const formatBytes = (bytes: any, decimals = 1) => {
|
||||
export const formatBytes = (bytes: number, decimals = 1) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
@@ -79,10 +81,12 @@ export const downloadFile = (blob: Blob, filename: string): void => {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const readFileAsArrayBuffer = (file: any) => {
|
||||
export const readFileAsArrayBuffer = (
|
||||
file: Blob
|
||||
): Promise<ArrayBuffer | null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onload = () => resolve(reader.result as ArrayBuffer | null);
|
||||
reader.onerror = (error) => reject(error);
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
@@ -139,7 +143,7 @@ export function parsePageRanges(
|
||||
* @param {string} isoDateString - The ISO 8601 date string.
|
||||
* @returns {string} A localized date and time string, or the original string if parsing fails.
|
||||
*/
|
||||
export function formatIsoDate(isoDateString) {
|
||||
export function formatIsoDate(isoDateString: string) {
|
||||
if (!isoDateString || typeof isoDateString !== 'string') {
|
||||
return isoDateString; // Return original value if it's not a valid string
|
||||
}
|
||||
@@ -156,20 +160,20 @@ export function formatIsoDate(isoDateString) {
|
||||
}
|
||||
}
|
||||
|
||||
let qpdfInstance: any = null;
|
||||
let qpdfInstance: QpdfInstanceExtended | null = null;
|
||||
|
||||
/**
|
||||
* Initialize qpdf-wasm singleton.
|
||||
* Subsequent calls return the same instance.
|
||||
*/
|
||||
export async function initializeQpdf() {
|
||||
export async function initializeQpdf(): Promise<QpdfInstanceExtended> {
|
||||
if (qpdfInstance) return qpdfInstance;
|
||||
|
||||
showLoader('Initializing PDF engine...');
|
||||
try {
|
||||
qpdfInstance = await createModule({
|
||||
qpdfInstance = (await createModule({
|
||||
locateFile: () => import.meta.env.BASE_URL + 'qpdf.wasm',
|
||||
});
|
||||
})) as unknown as QpdfInstanceExtended;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize qpdf-wasm:', error);
|
||||
showAlert(
|
||||
@@ -267,24 +271,19 @@ export function resetAndReloadTool(preResetCallback?: () => void) {
|
||||
* @param src The source to load (url string, typed array, or parameters object)
|
||||
* @returns The PDF loading task
|
||||
*/
|
||||
export function getPDFDocument(src: any) {
|
||||
let params = src;
|
||||
export function getPDFDocument(
|
||||
src: string | Uint8Array | ArrayBuffer | DocumentInitParameters
|
||||
) {
|
||||
let params: DocumentInitParameters;
|
||||
|
||||
// Handle different input types similar to how getDocument handles them,
|
||||
// but we ensure we have an object to attach wasmUrl to.
|
||||
if (typeof src === 'string') {
|
||||
params = { url: src };
|
||||
} else if (src instanceof Uint8Array || src instanceof ArrayBuffer) {
|
||||
params = { data: src };
|
||||
} else {
|
||||
params = src;
|
||||
}
|
||||
|
||||
// Ensure params is an object
|
||||
if (typeof params !== 'object' || params === null) {
|
||||
params = {};
|
||||
}
|
||||
|
||||
// Add wasmUrl pointing to our public/wasm directory
|
||||
// This is required for PDF.js v5+ to load OpenJPEG for certain images
|
||||
return pdfjsLib.getDocument({
|
||||
...params,
|
||||
wasmUrl: import.meta.env.BASE_URL + 'pdfjs-viewer/wasm/',
|
||||
@@ -413,7 +412,7 @@ export function formatRawDate(raw: string): string {
|
||||
year,
|
||||
hoursStr,
|
||||
minsStr,
|
||||
secsStr,
|
||||
_secsStr,
|
||||
timezone,
|
||||
] = match;
|
||||
|
||||
@@ -455,8 +454,8 @@ export function formatRawDate(raw: string): string {
|
||||
|
||||
return `${fullDay}, ${fullMonth} ${dom}, ${year} at ${hours}:${minsStr} ${ampm} (${formattedTz})`;
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to raw string if parsing fails
|
||||
} catch {
|
||||
console.error('Error parsing date string:', raw);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
+143
-121
@@ -1,18 +1,19 @@
|
||||
/**
|
||||
* LibreOffice WASM Converter Wrapper
|
||||
*
|
||||
*
|
||||
* Uses @matbee/libreoffice-converter package for document conversion.
|
||||
* Handles progress tracking and provides simpler API.
|
||||
*/
|
||||
|
||||
import { WorkerBrowserConverter } from '@matbee/libreoffice-converter/browser';
|
||||
import type { InputFormat } from '@matbee/libreoffice-converter/browser';
|
||||
|
||||
const LIBREOFFICE_LOCAL_PATH = import.meta.env.BASE_URL + 'libreoffice-wasm/';
|
||||
|
||||
export interface LoadProgress {
|
||||
phase: 'loading' | 'initializing' | 'converting' | 'complete' | 'ready';
|
||||
percent: number;
|
||||
message: string;
|
||||
phase: 'loading' | 'initializing' | 'converting' | 'complete' | 'ready';
|
||||
percent: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type ProgressCallback = (progress: LoadProgress) => void;
|
||||
@@ -21,140 +22,161 @@ export type ProgressCallback = (progress: LoadProgress) => void;
|
||||
let converterInstance: LibreOfficeConverter | null = null;
|
||||
|
||||
export class LibreOfficeConverter {
|
||||
private converter: WorkerBrowserConverter | null = null;
|
||||
private initialized = false;
|
||||
private initializing = false;
|
||||
private basePath: string;
|
||||
private converter: WorkerBrowserConverter | null = null;
|
||||
private initialized = false;
|
||||
private initializing = false;
|
||||
private basePath: string;
|
||||
|
||||
constructor(basePath?: string) {
|
||||
this.basePath = basePath || LIBREOFFICE_LOCAL_PATH;
|
||||
constructor(basePath?: string) {
|
||||
this.basePath = basePath || LIBREOFFICE_LOCAL_PATH;
|
||||
}
|
||||
|
||||
async initialize(onProgress?: ProgressCallback): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
|
||||
if (this.initializing) {
|
||||
while (this.initializing) {
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async initialize(onProgress?: ProgressCallback): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
this.initializing = true;
|
||||
let progressCallback = onProgress; // Store original callback
|
||||
|
||||
if (this.initializing) {
|
||||
while (this.initializing) {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
progressCallback?.({
|
||||
phase: 'loading',
|
||||
percent: 0,
|
||||
message: 'Loading conversion engine...',
|
||||
});
|
||||
|
||||
|
||||
this.initializing = true;
|
||||
let progressCallback = onProgress; // Store original callback
|
||||
|
||||
try {
|
||||
progressCallback?.({ phase: 'loading', percent: 0, message: 'Loading conversion engine...' });
|
||||
|
||||
this.converter = new WorkerBrowserConverter({
|
||||
sofficeJs: `${this.basePath}soffice.js`,
|
||||
sofficeWasm: `${this.basePath}soffice.wasm.gz`,
|
||||
sofficeData: `${this.basePath}soffice.data.gz`,
|
||||
sofficeWorkerJs: `${this.basePath}soffice.worker.js`,
|
||||
browserWorkerJs: `${this.basePath}browser.worker.global.js`,
|
||||
verbose: false,
|
||||
onProgress: (info: { phase: string; percent: number; message: string }) => {
|
||||
if (progressCallback && !this.initialized) {
|
||||
const simplifiedMessage = `Loading conversion engine (${Math.round(info.percent)}%)...`;
|
||||
progressCallback({
|
||||
phase: info.phase as LoadProgress['phase'],
|
||||
percent: info.percent,
|
||||
message: simplifiedMessage
|
||||
});
|
||||
}
|
||||
},
|
||||
onReady: () => {
|
||||
console.log('[LibreOffice] Ready!');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
console.error('[LibreOffice] Error:', error);
|
||||
},
|
||||
this.converter = new WorkerBrowserConverter({
|
||||
sofficeJs: `${this.basePath}soffice.js`,
|
||||
sofficeWasm: `${this.basePath}soffice.wasm.gz`,
|
||||
sofficeData: `${this.basePath}soffice.data.gz`,
|
||||
sofficeWorkerJs: `${this.basePath}soffice.worker.js`,
|
||||
browserWorkerJs: `${this.basePath}browser.worker.global.js`,
|
||||
verbose: false,
|
||||
onProgress: (info: {
|
||||
phase: string;
|
||||
percent: number;
|
||||
message: string;
|
||||
}) => {
|
||||
if (progressCallback && !this.initialized) {
|
||||
const simplifiedMessage = `Loading conversion engine (${Math.round(info.percent)}%)...`;
|
||||
progressCallback({
|
||||
phase: info.phase as LoadProgress['phase'],
|
||||
percent: info.percent,
|
||||
message: simplifiedMessage,
|
||||
});
|
||||
}
|
||||
},
|
||||
onReady: () => {
|
||||
console.log('[LibreOffice] Ready!');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
console.error('[LibreOffice] Error:', error);
|
||||
},
|
||||
});
|
||||
|
||||
await this.converter.initialize();
|
||||
this.initialized = true;
|
||||
await this.converter.initialize();
|
||||
this.initialized = true;
|
||||
|
||||
// Call completion message
|
||||
progressCallback?.({ phase: 'ready', percent: 100, message: 'Conversion engine ready!' });
|
||||
// Call completion message
|
||||
progressCallback?.({
|
||||
phase: 'ready',
|
||||
percent: 100,
|
||||
message: 'Conversion engine ready!',
|
||||
});
|
||||
|
||||
// Null out the callback to prevent any late-firing progress updates
|
||||
progressCallback = undefined;
|
||||
} finally {
|
||||
this.initializing = false;
|
||||
}
|
||||
// Null out the callback to prevent any late-firing progress updates
|
||||
progressCallback = undefined;
|
||||
} finally {
|
||||
this.initializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.initialized && this.converter !== null;
|
||||
}
|
||||
|
||||
async convertToPdf(file: File): Promise<Blob> {
|
||||
if (!this.converter) {
|
||||
throw new Error('Converter not initialized');
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.initialized && this.converter !== null;
|
||||
console.log(`[LibreOffice] Converting ${file.name} to PDF...`);
|
||||
console.log(
|
||||
`[LibreOffice] File type: ${file.type}, Size: ${file.size} bytes`
|
||||
);
|
||||
|
||||
try {
|
||||
console.log(`[LibreOffice] Reading file as ArrayBuffer...`);
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
console.log(`[LibreOffice] File loaded, ${uint8Array.length} bytes`);
|
||||
|
||||
console.log(`[LibreOffice] Calling converter.convert() with buffer...`);
|
||||
const startTime = Date.now();
|
||||
|
||||
// Detect input format - critical for CSV to apply import filters
|
||||
const ext = file.name.split('.').pop()?.toLowerCase() || '';
|
||||
console.log(`[LibreOffice] Detected format from extension: ${ext}`);
|
||||
|
||||
const result = await this.converter.convert(
|
||||
uint8Array,
|
||||
{
|
||||
outputFormat: 'pdf',
|
||||
inputFormat: ext as InputFormat,
|
||||
},
|
||||
file.name
|
||||
);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(
|
||||
`[LibreOffice] Conversion complete! Duration: ${duration}ms, Size: ${result.data.length} bytes`
|
||||
);
|
||||
|
||||
// Create a copy to avoid SharedArrayBuffer type issues
|
||||
const data = new Uint8Array(result.data);
|
||||
return new Blob([data], { type: result.mimeType });
|
||||
} catch (error) {
|
||||
console.error(`[LibreOffice] Conversion FAILED for ${file.name}:`, error);
|
||||
console.error(`[LibreOffice] Error details:`, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async convertToPdf(file: File): Promise<Blob> {
|
||||
if (!this.converter) {
|
||||
throw new Error('Converter not initialized');
|
||||
}
|
||||
async wordToPdf(file: File): Promise<Blob> {
|
||||
return this.convertToPdf(file);
|
||||
}
|
||||
|
||||
console.log(`[LibreOffice] Converting ${file.name} to PDF...`);
|
||||
console.log(`[LibreOffice] File type: ${file.type}, Size: ${file.size} bytes`);
|
||||
async pptToPdf(file: File): Promise<Blob> {
|
||||
return this.convertToPdf(file);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[LibreOffice] Reading file as ArrayBuffer...`);
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
console.log(`[LibreOffice] File loaded, ${uint8Array.length} bytes`);
|
||||
async excelToPdf(file: File): Promise<Blob> {
|
||||
return this.convertToPdf(file);
|
||||
}
|
||||
|
||||
console.log(`[LibreOffice] Calling converter.convert() with buffer...`);
|
||||
const startTime = Date.now();
|
||||
|
||||
// Detect input format - critical for CSV to apply import filters
|
||||
const ext = file.name.split('.').pop()?.toLowerCase() || '';
|
||||
console.log(`[LibreOffice] Detected format from extension: ${ext}`);
|
||||
|
||||
const result = await this.converter.convert(uint8Array, {
|
||||
outputFormat: 'pdf',
|
||||
inputFormat: ext as any, // Explicitly specify format for CSV import filters
|
||||
}, file.name);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`[LibreOffice] Conversion complete! Duration: ${duration}ms, Size: ${result.data.length} bytes`);
|
||||
|
||||
// Create a copy to avoid SharedArrayBuffer type issues
|
||||
const data = new Uint8Array(result.data);
|
||||
return new Blob([data], { type: result.mimeType });
|
||||
} catch (error) {
|
||||
console.error(`[LibreOffice] Conversion FAILED for ${file.name}:`, error);
|
||||
console.error(`[LibreOffice] Error details:`, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async wordToPdf(file: File): Promise<Blob> {
|
||||
return this.convertToPdf(file);
|
||||
}
|
||||
|
||||
async pptToPdf(file: File): Promise<Blob> {
|
||||
return this.convertToPdf(file);
|
||||
}
|
||||
|
||||
async excelToPdf(file: File): Promise<Blob> {
|
||||
return this.convertToPdf(file);
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
if (this.converter) {
|
||||
await this.converter.destroy();
|
||||
}
|
||||
this.converter = null;
|
||||
this.initialized = false;
|
||||
async destroy(): Promise<void> {
|
||||
if (this.converter) {
|
||||
await this.converter.destroy();
|
||||
}
|
||||
this.converter = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLibreOfficeConverter(basePath?: string): LibreOfficeConverter {
|
||||
if (!converterInstance) {
|
||||
converterInstance = new LibreOfficeConverter(basePath);
|
||||
}
|
||||
return converterInstance;
|
||||
export function getLibreOfficeConverter(
|
||||
basePath?: string
|
||||
): LibreOfficeConverter {
|
||||
if (!converterInstance) {
|
||||
converterInstance = new LibreOfficeConverter(basePath);
|
||||
}
|
||||
return converterInstance;
|
||||
}
|
||||
|
||||
+123
-71
@@ -29,8 +29,14 @@ import taskLists from 'markdown-it-task-lists';
|
||||
import anchor from 'markdown-it-anchor';
|
||||
import tocDoneRight from 'markdown-it-toc-done-right';
|
||||
import { applyTranslations } from '../i18n/i18n';
|
||||
|
||||
|
||||
import type {
|
||||
WindowWithLucide,
|
||||
WindowWithI18next,
|
||||
MarkdownItOptions,
|
||||
MarkdownItToken,
|
||||
MarkdownItRendererSelf,
|
||||
MarkdownItRenderRule,
|
||||
} from '@/types';
|
||||
|
||||
// Register highlight.js languages
|
||||
hljs.registerLanguage('javascript', javascript);
|
||||
@@ -66,18 +72,7 @@ export interface MarkdownEditorOptions {
|
||||
onBack?: () => void;
|
||||
}
|
||||
|
||||
export interface MarkdownItOptions {
|
||||
/** Enable HTML tags in source */
|
||||
html: boolean;
|
||||
/** Convert '\n' in paragraphs into <br> */
|
||||
breaks: boolean;
|
||||
/** Autoconvert URL-like text to links */
|
||||
linkify: boolean;
|
||||
/** Enable some language-neutral replacement + quotes beautification */
|
||||
typographer: boolean;
|
||||
/** Highlight function for fenced code blocks */
|
||||
highlight?: (str: string, lang: string) => string;
|
||||
}
|
||||
export type { MarkdownItOptions } from '@/types';
|
||||
|
||||
const DEFAULT_MARKDOWN = `# Welcome to BentoPDF Markdown Editor
|
||||
|
||||
@@ -277,7 +272,7 @@ export class MarkdownEditor {
|
||||
html: true,
|
||||
breaks: false,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
typographer: true,
|
||||
};
|
||||
|
||||
constructor(container: HTMLElement, options: MarkdownEditorOptions) {
|
||||
@@ -303,22 +298,36 @@ export class MarkdownEditor {
|
||||
startOnLoad: false,
|
||||
theme: 'default',
|
||||
securityLevel: 'loose',
|
||||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
});
|
||||
this.mermaidInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private configureLinkRenderer(): void {
|
||||
// Override link renderer to add target="_blank" and rel="noopener"
|
||||
const defaultRender = this.md.renderer.rules.link_open ||
|
||||
((tokens: any[], idx: number, options: any, _env: any, self: any) => self.renderToken(tokens, idx, options));
|
||||
const existingRule = this.md.renderer.rules.link_open;
|
||||
const defaultRender: MarkdownItRenderRule = existingRule
|
||||
? (existingRule as unknown as MarkdownItRenderRule)
|
||||
: (
|
||||
tokens: MarkdownItToken[],
|
||||
idx: number,
|
||||
options: MarkdownItOptions,
|
||||
env: unknown,
|
||||
self: MarkdownItRendererSelf
|
||||
) => self.renderToken(tokens, idx, options);
|
||||
|
||||
this.md.renderer.rules.link_open = (tokens: any[], idx: number, options: any, env: any, self: any) => {
|
||||
const token = tokens[idx];
|
||||
this.md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
|
||||
const token = tokens[idx] as unknown as MarkdownItToken;
|
||||
token.attrSet('target', '_blank');
|
||||
token.attrSet('rel', 'noopener noreferrer');
|
||||
return defaultRender(tokens, idx, options, env, self);
|
||||
return defaultRender(
|
||||
tokens as unknown as MarkdownItToken[],
|
||||
idx,
|
||||
options as unknown as MarkdownItOptions,
|
||||
env as unknown,
|
||||
self as unknown as MarkdownItRendererSelf
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -417,12 +426,11 @@ export class MarkdownEditor {
|
||||
this.applyI18n();
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (typeof (window as any).lucide !== 'undefined') {
|
||||
(window as any).lucide.createIcons();
|
||||
if (typeof (window as WindowWithLucide).lucide !== 'undefined') {
|
||||
(window as WindowWithLucide).lucide?.createIcons();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private setupEventListeners(): void {
|
||||
// Editor input
|
||||
this.editor?.addEventListener('input', () => {
|
||||
@@ -441,9 +449,13 @@ export class MarkdownEditor {
|
||||
this.editor?.addEventListener('scroll', () => {
|
||||
if (this.syncScroll && !this.isSyncing && this.editor && this.preview) {
|
||||
this.isSyncing = true;
|
||||
const scrollPercentage = this.editor.scrollTop / (this.editor.scrollHeight - this.editor.clientHeight);
|
||||
this.preview.scrollTop = scrollPercentage * (this.preview.scrollHeight - this.preview.clientHeight);
|
||||
setTimeout(() => this.isSyncing = false, 10);
|
||||
const scrollPercentage =
|
||||
this.editor.scrollTop /
|
||||
(this.editor.scrollHeight - this.editor.clientHeight);
|
||||
this.preview.scrollTop =
|
||||
scrollPercentage *
|
||||
(this.preview.scrollHeight - this.preview.clientHeight);
|
||||
setTimeout(() => (this.isSyncing = false), 10);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -451,9 +463,13 @@ export class MarkdownEditor {
|
||||
this.preview?.addEventListener('scroll', () => {
|
||||
if (this.syncScroll && !this.isSyncing && this.editor && this.preview) {
|
||||
this.isSyncing = true;
|
||||
const scrollPercentage = this.preview.scrollTop / (this.preview.scrollHeight - this.preview.clientHeight);
|
||||
this.editor.scrollTop = scrollPercentage * (this.editor.scrollHeight - this.editor.clientHeight);
|
||||
setTimeout(() => this.isSyncing = false, 10);
|
||||
const scrollPercentage =
|
||||
this.preview.scrollTop /
|
||||
(this.preview.scrollHeight - this.preview.clientHeight);
|
||||
this.editor.scrollTop =
|
||||
scrollPercentage *
|
||||
(this.editor.scrollHeight - this.editor.clientHeight);
|
||||
setTimeout(() => (this.isSyncing = false), 10);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -474,22 +490,30 @@ export class MarkdownEditor {
|
||||
});
|
||||
|
||||
// Settings modal close
|
||||
document.getElementById('mdCloseSettings')?.addEventListener('click', () => {
|
||||
const modal = document.getElementById('mdSettingsModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal on overlay click
|
||||
document.getElementById('mdSettingsModal')?.addEventListener('click', (e) => {
|
||||
if ((e.target as HTMLElement).classList.contains('md-editor-modal-overlay')) {
|
||||
document
|
||||
.getElementById('mdCloseSettings')
|
||||
?.addEventListener('click', () => {
|
||||
const modal = document.getElementById('mdSettingsModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Close modal on overlay click
|
||||
document
|
||||
.getElementById('mdSettingsModal')
|
||||
?.addEventListener('click', (e) => {
|
||||
if (
|
||||
(e.target as HTMLElement).classList.contains(
|
||||
'md-editor-modal-overlay'
|
||||
)
|
||||
) {
|
||||
const modal = document.getElementById('mdSettingsModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Settings checkboxes
|
||||
document.getElementById('mdOptHtml')?.addEventListener('change', (e) => {
|
||||
@@ -507,10 +531,12 @@ export class MarkdownEditor {
|
||||
this.updateMarkdownIt();
|
||||
});
|
||||
|
||||
document.getElementById('mdOptTypographer')?.addEventListener('change', (e) => {
|
||||
this.mdOptions.typographer = (e.target as HTMLInputElement).checked;
|
||||
this.updateMarkdownIt();
|
||||
});
|
||||
document
|
||||
.getElementById('mdOptTypographer')
|
||||
?.addEventListener('change', (e) => {
|
||||
this.mdOptions.typographer = (e.target as HTMLInputElement).checked;
|
||||
this.updateMarkdownIt();
|
||||
});
|
||||
|
||||
// Preset selector
|
||||
document.getElementById('mdPreset')?.addEventListener('change', (e) => {
|
||||
@@ -549,7 +575,8 @@ export class MarkdownEditor {
|
||||
const start = this.editor!.selectionStart;
|
||||
const end = this.editor!.selectionEnd;
|
||||
const value = this.editor!.value;
|
||||
this.editor!.value = value.substring(0, start) + ' ' + value.substring(end);
|
||||
this.editor!.value =
|
||||
value.substring(0, start) + ' ' + value.substring(end);
|
||||
this.editor!.selectionStart = this.editor!.selectionEnd = start + 2;
|
||||
this.updatePreview();
|
||||
}
|
||||
@@ -563,18 +590,37 @@ export class MarkdownEditor {
|
||||
|
||||
// Update options based on preset
|
||||
if (preset === 'commonmark') {
|
||||
this.mdOptions = { html: false, breaks: false, linkify: false, typographer: false };
|
||||
this.mdOptions = {
|
||||
html: false,
|
||||
breaks: false,
|
||||
linkify: false,
|
||||
typographer: false,
|
||||
};
|
||||
} else if (preset === 'zero') {
|
||||
this.mdOptions = { html: false, breaks: false, linkify: false, typographer: false };
|
||||
this.mdOptions = {
|
||||
html: false,
|
||||
breaks: false,
|
||||
linkify: false,
|
||||
typographer: false,
|
||||
};
|
||||
} else {
|
||||
this.mdOptions = { html: true, breaks: false, linkify: true, typographer: true };
|
||||
this.mdOptions = {
|
||||
html: true,
|
||||
breaks: false,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Update UI checkboxes
|
||||
(document.getElementById('mdOptHtml') as HTMLInputElement).checked = this.mdOptions.html;
|
||||
(document.getElementById('mdOptBreaks') as HTMLInputElement).checked = this.mdOptions.breaks;
|
||||
(document.getElementById('mdOptLinkify') as HTMLInputElement).checked = this.mdOptions.linkify;
|
||||
(document.getElementById('mdOptTypographer') as HTMLInputElement).checked = this.mdOptions.typographer;
|
||||
(document.getElementById('mdOptHtml') as HTMLInputElement).checked =
|
||||
this.mdOptions.html;
|
||||
(document.getElementById('mdOptBreaks') as HTMLInputElement).checked =
|
||||
this.mdOptions.breaks;
|
||||
(document.getElementById('mdOptLinkify') as HTMLInputElement).checked =
|
||||
this.mdOptions.linkify;
|
||||
(document.getElementById('mdOptTypographer') as HTMLInputElement).checked =
|
||||
this.mdOptions.typographer;
|
||||
|
||||
this.updateMarkdownIt();
|
||||
}
|
||||
@@ -588,7 +634,6 @@ export class MarkdownEditor {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private createMarkdownIt(): MarkdownIt {
|
||||
// Use preset if commonmark or zero
|
||||
let md: MarkdownIt;
|
||||
@@ -604,28 +649,31 @@ export class MarkdownEditor {
|
||||
highlight: (str: string, lang: string) => {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
|
||||
return hljs.highlight(str, {
|
||||
language: lang,
|
||||
ignoreIllegals: true,
|
||||
}).value;
|
||||
} catch {
|
||||
// Fall through to default
|
||||
}
|
||||
}
|
||||
return ''; // Use external default escaping
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Apply plugins only for default preset (plugins may not work well with commonmark/zero)
|
||||
if (this.currentPreset === 'default') {
|
||||
md.use(sub) // Subscript: ~text~ -> <sub>text</sub>
|
||||
.use(sup) // Superscript: ^text^ -> <sup>text</sup>
|
||||
.use(footnote) // Footnotes: [^1] and [^1]: footnote text
|
||||
.use(deflist) // Definition lists
|
||||
.use(abbr) // Abbreviations: *[abbr]: full text
|
||||
.use(emoji) // Emoji: :smile: -> 😄
|
||||
.use(ins) // Inserted text: ++text++ -> <ins>text</ins>
|
||||
.use(mark) // Marked text: ==text== -> <mark>text</mark>
|
||||
.use(taskLists, { enabled: true, label: true, labelAfter: true }) // Task lists: - [x] done
|
||||
.use(anchor, { permalink: false }) // Header anchors
|
||||
md.use(sub) // Subscript: ~text~ -> <sub>text</sub>
|
||||
.use(sup) // Superscript: ^text^ -> <sup>text</sup>
|
||||
.use(footnote) // Footnotes: [^1] and [^1]: footnote text
|
||||
.use(deflist) // Definition lists
|
||||
.use(abbr) // Abbreviations: *[abbr]: full text
|
||||
.use(emoji) // Emoji: :smile: -> 😄
|
||||
.use(ins) // Inserted text: ++text++ -> <ins>text</ins>
|
||||
.use(mark) // Marked text: ==text== -> <mark>text</mark>
|
||||
.use(taskLists, { enabled: true, label: true, labelAfter: true }) // Task lists: - [x] done
|
||||
.use(anchor, { permalink: false }) // Header anchors
|
||||
.use(tocDoneRight); // Table of contents: ${toc}
|
||||
}
|
||||
|
||||
@@ -650,7 +698,9 @@ export class MarkdownEditor {
|
||||
private async renderMermaidDiagrams(): Promise<void> {
|
||||
if (!this.preview) return;
|
||||
|
||||
const mermaidBlocks = this.preview.querySelectorAll('pre > code.language-mermaid');
|
||||
const mermaidBlocks = this.preview.querySelectorAll(
|
||||
'pre > code.language-mermaid'
|
||||
);
|
||||
|
||||
for (let i = 0; i < mermaidBlocks.length; i++) {
|
||||
const block = mermaidBlocks[i] as HTMLElement;
|
||||
@@ -948,14 +998,16 @@ ${content}
|
||||
applyTranslations();
|
||||
|
||||
// Special handling for select options (data-i18n on options doesn't work with applyTranslations)
|
||||
const presetSelect = document.getElementById('mdPreset') as HTMLSelectElement;
|
||||
const presetSelect = document.getElementById(
|
||||
'mdPreset'
|
||||
) as HTMLSelectElement;
|
||||
if (presetSelect) {
|
||||
const options = presetSelect.querySelectorAll('option[data-i18n]');
|
||||
options.forEach((option) => {
|
||||
const key = option.getAttribute('data-i18n');
|
||||
if (key) {
|
||||
// Use i18next directly for option text
|
||||
const translated = (window as any).i18next?.t(key);
|
||||
const translated = (window as WindowWithI18next).i18next?.t(key);
|
||||
if (translated && translated !== key) {
|
||||
option.textContent = translated;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ document.addEventListener('keydown', handleKeydown);
|
||||
export function initPagePreview(
|
||||
container: HTMLElement,
|
||||
pdfjsDoc: PDFDocumentProxy,
|
||||
options: { pageAttr?: string } = {}
|
||||
_options: { pageAttr?: string } = {}
|
||||
): void {
|
||||
const totalPages = pdfjsDoc.numPages;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getCpdf, isCpdfAvailable } from './cpdf-helper';
|
||||
import { isPyMuPDFAvailable, loadPyMuPDF } from './pymupdf-loader';
|
||||
import type { CpdfInstance } from '@/types';
|
||||
|
||||
export type PdfDecryptEngine = 'cpdf' | 'pymupdf';
|
||||
|
||||
@@ -37,13 +38,13 @@ function copyBytes(bytes: Uint8Array): Uint8Array {
|
||||
return Uint8Array.from(bytes);
|
||||
}
|
||||
|
||||
function cleanupCpdfDocument(cpdf: unknown, pdf: unknown): void {
|
||||
if (!cpdf || !pdf || typeof cpdf !== 'object' || !('deletePdf' in cpdf)) {
|
||||
function cleanupCpdfDocument(cpdf: CpdfInstance, pdf: unknown): void {
|
||||
if (!cpdf || !pdf) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
(cpdf as { deletePdf: (document: unknown) => void }).deletePdf(pdf);
|
||||
cpdf.deletePdf(pdf);
|
||||
} catch (cleanupError) {
|
||||
console.warn(
|
||||
`${DECRYPT_LOG_PREFIX} Failed to cleanup CoherentPDF document: ${normalizeErrorMessage(cleanupError)}`
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
import { WasmProvider } from './wasm-provider.js';
|
||||
import type {
|
||||
PyMuPDFInstance,
|
||||
PyMuPDFCompressOptions,
|
||||
PyMuPDFExtractTextOptions,
|
||||
PyMuPDFRasterizeOptions,
|
||||
} from '@/types';
|
||||
|
||||
let cachedPyMuPDF: any = null;
|
||||
let loadPromise: Promise<any> | null = null;
|
||||
let cachedPyMuPDF: PyMuPDFInstance | null = null;
|
||||
let loadPromise: Promise<PyMuPDFInstance> | null = null;
|
||||
|
||||
export interface PyMuPDFInterface {
|
||||
load(): Promise<void>;
|
||||
compressPdf(
|
||||
file: Blob,
|
||||
options: any
|
||||
options: PyMuPDFCompressOptions
|
||||
): Promise<{ blob: Blob; compressedSize: number }>;
|
||||
convertToPdf(file: Blob, ext: string): Promise<Blob>;
|
||||
extractText(file: Blob, options?: any): Promise<string>;
|
||||
extractText(file: Blob, options?: PyMuPDFExtractTextOptions): Promise<string>;
|
||||
extractImages(file: Blob): Promise<Array<{ data: Uint8Array; ext: string }>>;
|
||||
extractTables(file: Blob): Promise<any[]>;
|
||||
extractTables(file: Blob): Promise<unknown[]>;
|
||||
toSvg(file: Blob, pageNum: number): Promise<string>;
|
||||
renderPageToImage(file: Blob, pageNum: number, scale: number): Promise<Blob>;
|
||||
getPageCount(file: Blob): Promise<number>;
|
||||
rasterizePdf(file: Blob | File, options: any): Promise<Blob>;
|
||||
rasterizePdf(
|
||||
file: Blob | File,
|
||||
options: PyMuPDFRasterizeOptions
|
||||
): Promise<Blob>;
|
||||
}
|
||||
|
||||
export async function loadPyMuPDF(): Promise<any> {
|
||||
export async function loadPyMuPDF(): Promise<PyMuPDFInstance> {
|
||||
if (cachedPyMuPDF) {
|
||||
return cachedPyMuPDF;
|
||||
}
|
||||
@@ -65,9 +74,10 @@ export async function loadPyMuPDF(): Promise<any> {
|
||||
|
||||
console.log('[PyMuPDF Loader] Successfully loaded from CDN');
|
||||
return cachedPyMuPDF;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
loadPromise = null;
|
||||
throw new Error(`Failed to load PyMuPDF from CDN: ${error.message}`, {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Failed to load PyMuPDF from CDN: ${msg}`, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
+175
-121
@@ -1,5 +1,34 @@
|
||||
import { PDFDocument, PDFName } from 'pdf-lib';
|
||||
import { PDFDocument, PDFName, PDFDict, PDFArray } from 'pdf-lib';
|
||||
import { loadPdfDocument } from './load-pdf-document.js';
|
||||
import type { PDFDocumentInternal } from '@/types';
|
||||
|
||||
function getCatalogDict(pdfDoc: PDFDocument): PDFDict {
|
||||
return pdfDoc.catalog;
|
||||
}
|
||||
|
||||
function lookupAsDict(
|
||||
pdfDoc: PDFDocument,
|
||||
ref: ReturnType<PDFDict['get']>
|
||||
): PDFDict | undefined {
|
||||
if (!ref) return undefined;
|
||||
const result = pdfDoc.context.lookup(ref);
|
||||
if (result instanceof PDFDict) return result;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function lookupAsArray(
|
||||
pdfDoc: PDFDocument,
|
||||
ref: ReturnType<PDFDict['get']>
|
||||
): PDFArray | undefined {
|
||||
if (!ref) return undefined;
|
||||
const result = pdfDoc.context.lookup(ref);
|
||||
if (result instanceof PDFArray) return result;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getErrorMessage(e: unknown): string {
|
||||
return e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
|
||||
export interface SanitizeOptions {
|
||||
flattenForms: boolean;
|
||||
@@ -28,9 +57,9 @@ export const defaultSanitizeOptions: SanitizeOptions = {
|
||||
};
|
||||
|
||||
function removeMetadataFromDoc(pdfDoc: PDFDocument) {
|
||||
const infoDict = (pdfDoc as any).getInfoDict();
|
||||
const infoDict = (pdfDoc as unknown as PDFDocumentInternal).getInfoDict();
|
||||
const allKeys = infoDict.keys();
|
||||
allKeys.forEach((key: any) => {
|
||||
allKeys.forEach((key: PDFName) => {
|
||||
infoDict.delete(key);
|
||||
});
|
||||
|
||||
@@ -42,30 +71,30 @@ function removeMetadataFromDoc(pdfDoc: PDFDocument) {
|
||||
pdfDoc.setProducer('');
|
||||
|
||||
try {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
if (catalogDict.has(PDFName.of('Metadata'))) {
|
||||
catalogDict.delete(PDFName.of('Metadata'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove XMP metadata:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not remove XMP metadata:', getErrorMessage(e));
|
||||
}
|
||||
|
||||
try {
|
||||
const context = pdfDoc.context;
|
||||
if ((context as any).trailerInfo) {
|
||||
delete (context as any).trailerInfo.ID;
|
||||
if (context.trailerInfo) {
|
||||
delete context.trailerInfo.ID;
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove document IDs:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not remove document IDs:', getErrorMessage(e));
|
||||
}
|
||||
|
||||
try {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
if (catalogDict.has(PDFName.of('PieceInfo'))) {
|
||||
catalogDict.delete(PDFName.of('PieceInfo'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove PieceInfo:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not remove PieceInfo:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +103,11 @@ function removeAnnotationsFromDoc(pdfDoc: PDFDocument) {
|
||||
for (const page of pages) {
|
||||
try {
|
||||
page.node.delete(PDFName.of('Annots'));
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove annotations from page:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn(
|
||||
'Could not remove annotations from page:',
|
||||
getErrorMessage(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,21 +118,22 @@ function flattenFormsInDoc(pdfDoc: PDFDocument) {
|
||||
}
|
||||
|
||||
function removeJavascriptFromDoc(pdfDoc: PDFDocument) {
|
||||
if ((pdfDoc as any).javaScripts && (pdfDoc as any).javaScripts.length > 0) {
|
||||
(pdfDoc as any).javaScripts = [];
|
||||
const pdfDocInternal = pdfDoc as unknown as PDFDocumentInternal;
|
||||
if (pdfDocInternal.javaScripts && pdfDocInternal.javaScripts.length > 0) {
|
||||
pdfDocInternal.javaScripts = [];
|
||||
}
|
||||
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
|
||||
const namesRef = catalogDict.get(PDFName.of('Names'));
|
||||
if (namesRef) {
|
||||
try {
|
||||
const namesDict = pdfDoc.context.lookup(namesRef) as any;
|
||||
if (namesDict.has(PDFName.of('JavaScript'))) {
|
||||
const namesDict = lookupAsDict(pdfDoc, namesRef);
|
||||
if (namesDict?.has(PDFName.of('JavaScript'))) {
|
||||
namesDict.delete(PDFName.of('JavaScript'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not access Names/JavaScript:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not access Names/JavaScript:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,50 +157,55 @@ function removeJavascriptFromDoc(pdfDoc: PDFDocument) {
|
||||
const annotRefs = pageDict.Annots()?.asArray() || [];
|
||||
for (const annotRef of annotRefs) {
|
||||
try {
|
||||
const annot = pdfDoc.context.lookup(annotRef) as any;
|
||||
const annot = lookupAsDict(pdfDoc, annotRef);
|
||||
if (!annot) continue;
|
||||
|
||||
if (annot.has(PDFName.of('A'))) {
|
||||
const actionRef = annot.get(PDFName.of('A'));
|
||||
try {
|
||||
const actionDict = pdfDoc.context.lookup(actionRef) as any;
|
||||
const actionDict = lookupAsDict(pdfDoc, actionRef);
|
||||
const actionType = actionDict
|
||||
.get(PDFName.of('S'))
|
||||
?.get(PDFName.of('S'))
|
||||
?.toString()
|
||||
.substring(1);
|
||||
|
||||
if (actionType === 'JavaScript') {
|
||||
annot.delete(PDFName.of('A'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not read action:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not read action:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
if (annot.has(PDFName.of('AA'))) {
|
||||
annot.delete(PDFName.of('AA'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not process annotation for JS:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn(
|
||||
'Could not process annotation for JS:',
|
||||
getErrorMessage(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove page actions:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not remove page actions:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const acroFormRef = catalogDict.get(PDFName.of('AcroForm'));
|
||||
if (acroFormRef) {
|
||||
const acroFormDict = pdfDoc.context.lookup(acroFormRef) as any;
|
||||
const fieldsRef = acroFormDict.get(PDFName.of('Fields'));
|
||||
const acroFormDict = lookupAsDict(pdfDoc, acroFormRef);
|
||||
const fieldsRef = acroFormDict?.get(PDFName.of('Fields'));
|
||||
|
||||
if (fieldsRef) {
|
||||
const fieldsArray = pdfDoc.context.lookup(fieldsRef) as any;
|
||||
const fields = fieldsArray.asArray();
|
||||
const fieldsArray = lookupAsArray(pdfDoc, fieldsRef);
|
||||
const fields = fieldsArray?.asArray() || [];
|
||||
|
||||
for (const fieldRef of fields) {
|
||||
try {
|
||||
const field = pdfDoc.context.lookup(fieldRef) as any;
|
||||
const field = lookupAsDict(pdfDoc, fieldRef);
|
||||
if (!field) continue;
|
||||
|
||||
if (field.has(PDFName.of('A'))) {
|
||||
field.delete(PDFName.of('A'));
|
||||
@@ -176,29 +214,29 @@ function removeJavascriptFromDoc(pdfDoc: PDFDocument) {
|
||||
if (field.has(PDFName.of('AA'))) {
|
||||
field.delete(PDFName.of('AA'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not process field for JS:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not process field for JS:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not process form fields for JS:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not process form fields for JS:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
function removeEmbeddedFilesFromDoc(pdfDoc: PDFDocument) {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
|
||||
const namesRef = catalogDict.get(PDFName.of('Names'));
|
||||
if (namesRef) {
|
||||
try {
|
||||
const namesDict = pdfDoc.context.lookup(namesRef) as any;
|
||||
if (namesDict.has(PDFName.of('EmbeddedFiles'))) {
|
||||
const namesDict = lookupAsDict(pdfDoc, namesRef);
|
||||
if (namesDict?.has(PDFName.of('EmbeddedFiles'))) {
|
||||
namesDict.delete(PDFName.of('EmbeddedFiles'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not access Names/EmbeddedFiles:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not access Names/EmbeddedFiles:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,16 +252,16 @@ function removeEmbeddedFilesFromDoc(pdfDoc: PDFDocument) {
|
||||
|
||||
for (const ref of annotRefs) {
|
||||
try {
|
||||
const annot = pdfDoc.context.lookup(ref) as any;
|
||||
const annot = lookupAsDict(pdfDoc, ref);
|
||||
const subtype = annot
|
||||
.get(PDFName.of('Subtype'))
|
||||
?.get(PDFName.of('Subtype'))
|
||||
?.toString()
|
||||
.substring(1);
|
||||
|
||||
if (subtype !== 'FileAttachment') {
|
||||
annotsToKeep.push(ref);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
annotsToKeep.push(ref);
|
||||
}
|
||||
}
|
||||
@@ -236,18 +274,16 @@ function removeEmbeddedFilesFromDoc(pdfDoc: PDFDocument) {
|
||||
page.node.delete(PDFName.of('Annots'));
|
||||
}
|
||||
}
|
||||
} catch (pageError: any) {
|
||||
} catch (pageError: unknown) {
|
||||
console.warn(
|
||||
`Could not process page for attachments: ${pageError.message}`
|
||||
`Could not process page for attachments: ${getErrorMessage(pageError)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(pdfDoc as any).embeddedFiles &&
|
||||
(pdfDoc as any).embeddedFiles.length > 0
|
||||
) {
|
||||
(pdfDoc as any).embeddedFiles = [];
|
||||
const pdfDocInternal = pdfDoc as unknown as PDFDocumentInternal;
|
||||
if (pdfDocInternal.embeddedFiles && pdfDocInternal.embeddedFiles.length > 0) {
|
||||
pdfDocInternal.embeddedFiles = [];
|
||||
}
|
||||
|
||||
if (catalogDict.has(PDFName.of('Collection'))) {
|
||||
@@ -256,7 +292,7 @@ function removeEmbeddedFilesFromDoc(pdfDoc: PDFDocument) {
|
||||
}
|
||||
|
||||
function removeLayersFromDoc(pdfDoc: PDFDocument) {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
|
||||
if (catalogDict.has(PDFName.of('OCProperties'))) {
|
||||
catalogDict.delete(PDFName.of('OCProperties'));
|
||||
@@ -274,16 +310,16 @@ function removeLayersFromDoc(pdfDoc: PDFDocument) {
|
||||
const resourcesRef = pageDict.get(PDFName.of('Resources'));
|
||||
if (resourcesRef) {
|
||||
try {
|
||||
const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any;
|
||||
if (resourcesDict.has(PDFName.of('Properties'))) {
|
||||
const resourcesDict = lookupAsDict(pdfDoc, resourcesRef);
|
||||
if (resourcesDict?.has(PDFName.of('Properties'))) {
|
||||
resourcesDict.delete(PDFName.of('Properties'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not access Resources:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not access Resources:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove page layers:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not remove page layers:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,8 +335,9 @@ function removeLinksFromDoc(pdfDoc: PDFDocument) {
|
||||
const annotsRef = pageDict.get(PDFName.of('Annots'));
|
||||
if (!annotsRef) continue;
|
||||
|
||||
const annotsArray = pdfDoc.context.lookup(annotsRef) as any;
|
||||
const annotRefs = annotsArray.asArray();
|
||||
const annotsArrayObj = lookupAsArray(pdfDoc, annotsRef);
|
||||
if (!annotsArrayObj) continue;
|
||||
const annotRefs = annotsArrayObj.asArray();
|
||||
|
||||
if (annotRefs.length === 0) continue;
|
||||
|
||||
@@ -309,7 +346,11 @@ function removeLinksFromDoc(pdfDoc: PDFDocument) {
|
||||
|
||||
for (const ref of annotRefs) {
|
||||
try {
|
||||
const annot = pdfDoc.context.lookup(ref) as any;
|
||||
const annot = lookupAsDict(pdfDoc, ref);
|
||||
if (!annot) {
|
||||
annotsToKeep.push(ref);
|
||||
continue;
|
||||
}
|
||||
const subtype = annot
|
||||
.get(PDFName.of('Subtype'))
|
||||
?.toString()
|
||||
@@ -324,9 +365,9 @@ function removeLinksFromDoc(pdfDoc: PDFDocument) {
|
||||
const actionRef = annot.get(PDFName.of('A'));
|
||||
if (actionRef) {
|
||||
try {
|
||||
const actionDict = pdfDoc.context.lookup(actionRef) as any;
|
||||
const actionDict = lookupAsDict(pdfDoc, actionRef);
|
||||
const actionType = actionDict
|
||||
.get(PDFName.of('S'))
|
||||
?.get(PDFName.of('S'))
|
||||
?.toString()
|
||||
.substring(1);
|
||||
|
||||
@@ -339,8 +380,8 @@ function removeLinksFromDoc(pdfDoc: PDFDocument) {
|
||||
isLink = true;
|
||||
linksRemoved++;
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not read action:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not read action:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,8 +395,8 @@ function removeLinksFromDoc(pdfDoc: PDFDocument) {
|
||||
if (!isLink) {
|
||||
annotsToKeep.push(ref);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not process annotation:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not process annotation:', getErrorMessage(e));
|
||||
annotsToKeep.push(ref);
|
||||
}
|
||||
}
|
||||
@@ -368,37 +409,37 @@ function removeLinksFromDoc(pdfDoc: PDFDocument) {
|
||||
pageDict.delete(PDFName.of('Annots'));
|
||||
}
|
||||
}
|
||||
} catch (pageError: any) {
|
||||
} catch (pageError: unknown) {
|
||||
console.warn(
|
||||
`Could not process page ${pageIndex + 1} for links: ${pageError.message}`
|
||||
`Could not process page ${pageIndex + 1} for links: ${getErrorMessage(pageError)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
const namesRef = catalogDict.get(PDFName.of('Names'));
|
||||
if (namesRef) {
|
||||
try {
|
||||
const namesDict = pdfDoc.context.lookup(namesRef) as any;
|
||||
if (namesDict.has(PDFName.of('Dests'))) {
|
||||
const namesDict = lookupAsDict(pdfDoc, namesRef);
|
||||
if (namesDict?.has(PDFName.of('Dests'))) {
|
||||
namesDict.delete(PDFName.of('Dests'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not access Names/Dests:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not access Names/Dests:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
if (catalogDict.has(PDFName.of('Dests'))) {
|
||||
catalogDict.delete(PDFName.of('Dests'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove named destinations:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not remove named destinations:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
function removeStructureTreeFromDoc(pdfDoc: PDFDocument) {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
|
||||
if (catalogDict.has(PDFName.of('StructTreeRoot'))) {
|
||||
catalogDict.delete(PDFName.of('StructTreeRoot'));
|
||||
@@ -411,8 +452,8 @@ function removeStructureTreeFromDoc(pdfDoc: PDFDocument) {
|
||||
if (pageDict.has(PDFName.of('StructParents'))) {
|
||||
pageDict.delete(PDFName.of('StructParents'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not remove page StructParents:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn('Could not remove page StructParents:', getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +463,7 @@ function removeStructureTreeFromDoc(pdfDoc: PDFDocument) {
|
||||
}
|
||||
|
||||
function removeMarkInfoFromDoc(pdfDoc: PDFDocument) {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
|
||||
if (catalogDict.has(PDFName.of('MarkInfo'))) {
|
||||
catalogDict.delete(PDFName.of('MarkInfo'));
|
||||
@@ -444,29 +485,29 @@ function removeFontsFromDoc(pdfDoc: PDFDocument) {
|
||||
|
||||
if (resourcesRef) {
|
||||
try {
|
||||
const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any;
|
||||
const resourcesDict = lookupAsDict(pdfDoc, resourcesRef);
|
||||
if (!resourcesDict) continue;
|
||||
|
||||
if (resourcesDict.has(PDFName.of('Font'))) {
|
||||
const fontRef = resourcesDict.get(PDFName.of('Font'));
|
||||
|
||||
try {
|
||||
const fontDict = pdfDoc.context.lookup(fontRef) as any;
|
||||
const fontDict = lookupAsDict(pdfDoc, fontRef);
|
||||
if (!fontDict) continue;
|
||||
const fontKeys = fontDict.keys();
|
||||
|
||||
for (const fontKey of fontKeys) {
|
||||
try {
|
||||
const specificFontRef = fontDict.get(fontKey);
|
||||
const specificFont = pdfDoc.context.lookup(
|
||||
specificFontRef
|
||||
) as any;
|
||||
const specificFont = lookupAsDict(pdfDoc, specificFontRef);
|
||||
if (!specificFont) continue;
|
||||
|
||||
if (specificFont.has(PDFName.of('FontDescriptor'))) {
|
||||
const descriptorRef = specificFont.get(
|
||||
PDFName.of('FontDescriptor')
|
||||
);
|
||||
const descriptor = pdfDoc.context.lookup(
|
||||
descriptorRef
|
||||
) as any;
|
||||
const descriptor = lookupAsDict(pdfDoc, descriptorRef);
|
||||
if (!descriptor) continue;
|
||||
|
||||
const fontFileKeys = ['FontFile', 'FontFile2', 'FontFile3'];
|
||||
for (const key of fontFileKeys) {
|
||||
@@ -475,28 +516,38 @@ function removeFontsFromDoc(pdfDoc: PDFDocument) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not process font ${fontKey}:`, e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn(
|
||||
`Could not process font ${fontKey}:`,
|
||||
getErrorMessage(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not access font dictionary:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn(
|
||||
'Could not access font dictionary:',
|
||||
getErrorMessage(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn('Could not access Resources for fonts:', e.message);
|
||||
} catch (e: unknown) {
|
||||
console.warn(
|
||||
'Could not access Resources for fonts:',
|
||||
getErrorMessage(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.warn(
|
||||
`Could not remove fonts from page ${pageIndex + 1}:`,
|
||||
e.message
|
||||
getErrorMessage(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ((pdfDoc as any).fonts && (pdfDoc as any).fonts.length > 0) {
|
||||
(pdfDoc as any).fonts = [];
|
||||
const pdfDocInternal = pdfDoc as unknown as PDFDocumentInternal;
|
||||
if (pdfDocInternal.fonts && pdfDocInternal.fonts.length > 0) {
|
||||
pdfDocInternal.fonts = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,15 +560,18 @@ export async function sanitizePdf(
|
||||
if (options.flattenForms) {
|
||||
try {
|
||||
flattenFormsInDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not flatten forms: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not flatten forms: ${getErrorMessage(e)}`);
|
||||
try {
|
||||
const catalogDict = (pdfDoc.catalog as any).dict;
|
||||
const catalogDict = getCatalogDict(pdfDoc);
|
||||
if (catalogDict.has(PDFName.of('AcroForm'))) {
|
||||
catalogDict.delete(PDFName.of('AcroForm'));
|
||||
}
|
||||
} catch (removeError: any) {
|
||||
console.warn('Could not remove AcroForm:', removeError.message);
|
||||
} catch (removeError: unknown) {
|
||||
console.warn(
|
||||
'Could not remove AcroForm:',
|
||||
getErrorMessage(removeError)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -533,56 +587,56 @@ export async function sanitizePdf(
|
||||
if (options.removeJavascript) {
|
||||
try {
|
||||
removeJavascriptFromDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not remove JavaScript: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not remove JavaScript: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.removeEmbeddedFiles) {
|
||||
try {
|
||||
removeEmbeddedFilesFromDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not remove embedded files: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not remove embedded files: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.removeLayers) {
|
||||
try {
|
||||
removeLayersFromDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not remove layers: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not remove layers: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.removeLinks) {
|
||||
try {
|
||||
removeLinksFromDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not remove links: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not remove links: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.removeStructureTree) {
|
||||
try {
|
||||
removeStructureTreeFromDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not remove structure tree: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not remove structure tree: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.removeMarkInfo) {
|
||||
try {
|
||||
removeMarkInfoFromDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not remove MarkInfo: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not remove MarkInfo: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.removeFonts) {
|
||||
try {
|
||||
removeFontsFromDoc(pdfDoc);
|
||||
} catch (e: any) {
|
||||
console.warn(`Could not remove fonts: ${e.message}`);
|
||||
} catch (e: unknown) {
|
||||
console.warn(`Could not remove fonts: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getLibreOfficeConverter } from './libreoffice-loader.js';
|
||||
import { isWasmAvailable, getWasmBaseUrl } from '../config/wasm-cdn-config.js';
|
||||
|
||||
export enum PreloadStatus {
|
||||
@@ -77,7 +76,7 @@ async function preloadGhostscript(): Promise<void> {
|
||||
await import('./ghostscript-loader.js');
|
||||
|
||||
const gsModule = await loadGsModule();
|
||||
setCachedGsModule(gsModule as any);
|
||||
setCachedGsModule(gsModule);
|
||||
preloadState.ghostscript = PreloadStatus.READY;
|
||||
console.log('[Preloader] Ghostscript WASM ready');
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user