2026-01-27 15:26:11 +05:30
|
|
|
import { WasmProvider } from './wasm-provider.js';
|
2026-03-31 17:59:49 +05:30
|
|
|
import type {
|
|
|
|
|
GlobalScopeWithGhostscript,
|
|
|
|
|
GhostscriptDynamicInstance,
|
|
|
|
|
} from '@/types';
|
2026-01-27 15:26:11 +05:30
|
|
|
|
2026-03-31 17:59:49 +05:30
|
|
|
let cachedGS: GhostscriptInterface | null = null;
|
|
|
|
|
let loadPromise: Promise<GhostscriptInterface> | null = null;
|
2026-01-27 15:26:11 +05:30
|
|
|
|
|
|
|
|
export interface GhostscriptInterface {
|
|
|
|
|
convertToPDFA(pdfBuffer: ArrayBuffer, profile: string): Promise<ArrayBuffer>;
|
|
|
|
|
fontToOutline(pdfBuffer: ArrayBuffer): Promise<ArrayBuffer>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function loadGhostscript(): Promise<GhostscriptInterface> {
|
|
|
|
|
if (cachedGS) {
|
|
|
|
|
return cachedGS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (loadPromise) {
|
|
|
|
|
return loadPromise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadPromise = (async () => {
|
|
|
|
|
const baseUrl = WasmProvider.getUrl('ghostscript');
|
|
|
|
|
if (!baseUrl) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
'Ghostscript is not configured. Please configure it in Advanced Settings.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizedUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const wrapperUrl = `${normalizedUrl}gs.js`;
|
|
|
|
|
|
|
|
|
|
await loadScript(wrapperUrl);
|
|
|
|
|
|
2026-03-31 17:59:49 +05:30
|
|
|
const globalScope = (
|
|
|
|
|
typeof globalThis !== 'undefined' ? globalThis : window
|
|
|
|
|
) as typeof globalThis & GlobalScopeWithGhostscript;
|
2026-01-27 15:26:11 +05:30
|
|
|
|
2026-03-31 17:59:49 +05:30
|
|
|
if (typeof globalScope.loadGS === 'function') {
|
|
|
|
|
const instance = await globalScope.loadGS({
|
2026-01-27 15:26:11 +05:30
|
|
|
baseUrl: normalizedUrl,
|
|
|
|
|
});
|
2026-03-31 17:59:49 +05:30
|
|
|
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;
|
2026-01-27 15:26:11 +05:30
|
|
|
} else {
|
|
|
|
|
throw new Error(
|
|
|
|
|
'Ghostscript wrapper did not expose expected interface. Expected loadGS() or GhostscriptWASM class.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cachedGS;
|
2026-03-31 17:59:49 +05:30
|
|
|
} catch (error: unknown) {
|
2026-01-27 15:26:11 +05:30
|
|
|
loadPromise = null;
|
2026-03-31 17:59:49 +05:30
|
|
|
const msg = error instanceof Error ? error.message : String(error);
|
2026-01-27 15:26:11 +05:30
|
|
|
throw new Error(
|
2026-03-31 17:59:49 +05:30
|
|
|
`Failed to load Ghostscript from ${normalizedUrl}: ${msg}`,
|
2026-03-26 13:40:21 +05:30
|
|
|
{ cause: error }
|
2026-01-27 15:26:11 +05:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
return loadPromise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadScript(url: string): Promise<void> {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
if (document.querySelector(`script[src="${url}"]`)) {
|
|
|
|
|
resolve();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const script = document.createElement('script');
|
|
|
|
|
script.src = url;
|
|
|
|
|
script.type = 'text/javascript';
|
|
|
|
|
script.async = true;
|
|
|
|
|
|
|
|
|
|
script.onload = () => resolve();
|
|
|
|
|
script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
|
|
|
|
|
|
|
|
|
|
document.head.appendChild(script);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isGhostscriptAvailable(): boolean {
|
|
|
|
|
return WasmProvider.isConfigured('ghostscript');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function clearGhostscriptCache(): void {
|
|
|
|
|
cachedGS = null;
|
|
|
|
|
loadPromise = null;
|
|
|
|
|
}
|