mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 03:20:46 +02:00
perf(compression): add vite-plugin-compression for gzip and Brotli support (#6279)
Signed-off-by: Balázs Szücs <[email protected]>
This commit is contained in:
@@ -89,7 +89,7 @@ import { RedactionAPIBridge } from "@app/components/viewer/RedactionAPIBridge";
|
||||
import { DocumentPermissionsAPIBridge } from "@app/components/viewer/DocumentPermissionsAPIBridge";
|
||||
import { DocumentReadyWrapper } from "@app/components/viewer/DocumentReadyWrapper";
|
||||
import { ActiveDocumentProvider } from "@app/components/viewer/ActiveDocumentContext";
|
||||
import { absoluteWithBasePath } from "@app/constants/app";
|
||||
import { pdfiumWasmUrl } from "@app/services/wasmPrecompiler";
|
||||
import { FormFieldOverlay } from "@app/tools/formFill/FormFieldOverlay";
|
||||
import { ButtonAppearanceOverlay } from "@app/tools/formFill/ButtonAppearanceOverlay";
|
||||
import SignatureFieldOverlay from "@app/components/viewer/SignatureFieldOverlay";
|
||||
@@ -296,7 +296,7 @@ export function LocalEmbedPDF({
|
||||
|
||||
// Initialize the engine with the React hook - use local WASM for offline support
|
||||
const { engine, isLoading, error } = usePdfiumEngine({
|
||||
wasmUrl: absoluteWithBasePath("/pdfium/pdfium.wasm"),
|
||||
wasmUrl: pdfiumWasmUrl,
|
||||
});
|
||||
|
||||
// Early return if no file or URL provided
|
||||
|
||||
@@ -12,9 +12,34 @@
|
||||
* `getSignatures`, `saveAsCopy`, …) wrap the `PdfEngine` interface so callers
|
||||
* never have to deal with raw pointers or Tasks.
|
||||
*/
|
||||
import { init, type WrappedPdfiumModule } from "@embedpdf/pdfium";
|
||||
import {
|
||||
init,
|
||||
type WrappedPdfiumModule,
|
||||
type PdfiumModule,
|
||||
} from "@embedpdf/pdfium";
|
||||
import {
|
||||
pdfiumWasmModulePromise,
|
||||
startEagerWasmCompilation,
|
||||
pdfiumWasmUrl,
|
||||
} from "@app/services/wasmPrecompiler";
|
||||
import type { FormField, WidgetCoordinates } from "@app/tools/formFill/types";
|
||||
|
||||
interface ExtendedPdfiumRuntime {
|
||||
HEAPU8: Uint8Array;
|
||||
HEAPF32: Float32Array;
|
||||
}
|
||||
|
||||
interface PdfiumModuleOverrides extends Partial<PdfiumModule> {
|
||||
locateFile?: (url: string, scriptDirectory?: string) => string;
|
||||
instantiateWasm?: (
|
||||
imports: WebAssembly.Imports,
|
||||
successCallback: (
|
||||
instance: WebAssembly.Instance,
|
||||
module: WebAssembly.Module,
|
||||
) => void,
|
||||
) => void;
|
||||
}
|
||||
|
||||
// PDF form field type constants (matching PDFium C API FPDF_FORMFIELD_* values)
|
||||
const FPDF_FORMFIELD_UNKNOWN = 0;
|
||||
const FPDF_FORMFIELD_PUSHBUTTON = 1;
|
||||
@@ -46,8 +71,7 @@ let _module: WrappedPdfiumModule | null = null;
|
||||
* Resolve the absolute WASM URL using the same pattern as LocalEmbedPDF.
|
||||
*/
|
||||
function wasmUrl(): string {
|
||||
const base = (import.meta as any).env?.BASE_URL ?? "/";
|
||||
return `${base}pdfium/pdfium.wasm`.replace(/\/\//g, "/");
|
||||
return pdfiumWasmUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,9 +83,47 @@ function wasmUrl(): string {
|
||||
export async function getPdfiumModule(): Promise<WrappedPdfiumModule> {
|
||||
if (_module) return _module;
|
||||
if (!_initPromise) {
|
||||
_initPromise = init({
|
||||
// Ensure eager compilation has started if PDF service is requested before idle timeout
|
||||
startEagerWasmCompilation();
|
||||
|
||||
const overrides: PdfiumModuleOverrides = {
|
||||
locateFile: () => wasmUrl(),
|
||||
} as any).then((m) => {
|
||||
};
|
||||
|
||||
// Eagerly reuse pre-compiled WASM module from app boot if available
|
||||
overrides.instantiateWasm = (
|
||||
imports: WebAssembly.Imports,
|
||||
successCallback: (
|
||||
instance: WebAssembly.Instance,
|
||||
module: WebAssembly.Module,
|
||||
) => void,
|
||||
) => {
|
||||
pdfiumWasmModulePromise
|
||||
.then((wasmModule) => {
|
||||
if (wasmModule) {
|
||||
return WebAssembly.instantiate(wasmModule, imports).then(
|
||||
(instance) => {
|
||||
successCallback(instance, wasmModule);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
throw new Error("No pre-compiled WASM module found");
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.warn(
|
||||
"Eager WebAssembly instantiation failed, falling back to streaming compilation:",
|
||||
err,
|
||||
);
|
||||
WebAssembly.instantiateStreaming(fetch(wasmUrl()), imports).then(
|
||||
(result) => {
|
||||
successCallback(result.instance, result.module);
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
_initPromise = init(overrides as Partial<PdfiumModule>).then((m) => {
|
||||
// Call PDFiumExt_Init to ensure extensions (form fill etc.) are set up
|
||||
try {
|
||||
m.PDFiumExt_Init();
|
||||
@@ -110,9 +172,9 @@ export function readAnnotRectAdjusted(
|
||||
annotPtr: number,
|
||||
rectBuf: number,
|
||||
): boolean {
|
||||
const ext = (m as any).EPDFAnnot_GetRect;
|
||||
const ext = m.EPDFAnnot_GetRect;
|
||||
if (typeof ext === "function") {
|
||||
return ext.call(m, annotPtr, rectBuf);
|
||||
return ext(annotPtr, rectBuf);
|
||||
}
|
||||
return m.FPDFAnnot_GetRect(annotPtr, rectBuf);
|
||||
}
|
||||
@@ -186,15 +248,13 @@ export function readEffectivePageBox(
|
||||
let result: PageBox | null = null;
|
||||
try {
|
||||
// CropBox is the effective visible area
|
||||
if (
|
||||
(m as any).FPDFPage_GetCropBox(pagePtr, buf, buf + 4, buf + 8, buf + 12)
|
||||
) {
|
||||
if (m.FPDFPage_GetCropBox(pagePtr, buf, buf + 4, buf + 8, buf + 12)) {
|
||||
result = read();
|
||||
}
|
||||
// Fall back to MediaBox
|
||||
if (
|
||||
!result &&
|
||||
(m as any).FPDFPage_GetMediaBox(pagePtr, buf, buf + 4, buf + 8, buf + 12)
|
||||
m.FPDFPage_GetMediaBox(pagePtr, buf, buf + 4, buf + 8, buf + 12)
|
||||
) {
|
||||
result = read();
|
||||
}
|
||||
@@ -226,7 +286,7 @@ function copyToWasmHeap(
|
||||
bytes: Uint8Array,
|
||||
ptr: number,
|
||||
): void {
|
||||
new Uint8Array((m.pdfium.wasmExports as any).memory.buffer).set(bytes, ptr);
|
||||
(m.pdfium as typeof m.pdfium & ExtendedPdfiumRuntime).HEAPU8.set(bytes, ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1338,9 +1398,13 @@ async function renderWidgetAppearance(
|
||||
formEnvPtr: number,
|
||||
dpr: number,
|
||||
): Promise<ImageData | null> {
|
||||
const pdfiumWasm = m.pdfium as any;
|
||||
const matrixPtr = m.pdfium.wasmExports.malloc(6 * 4);
|
||||
const matrixView = new Float32Array(pdfiumWasm.HEAPF32.buffer, matrixPtr, 6);
|
||||
const pdfiumRuntime = m.pdfium as typeof m.pdfium & ExtendedPdfiumRuntime;
|
||||
const matrixView = new Float32Array(
|
||||
pdfiumRuntime.HEAPF32.buffer,
|
||||
matrixPtr,
|
||||
6,
|
||||
);
|
||||
const sx = wDev / pdfW;
|
||||
const sy = hDev / pdfH;
|
||||
matrixView.set([sx, 0, 0, -sy, -sx * annotLeft, sy * annotTop]);
|
||||
@@ -1364,7 +1428,7 @@ async function renderWidgetAppearance(
|
||||
let imageData: ImageData | null = null;
|
||||
if (ok) {
|
||||
const rgba = new Uint8ClampedArray(
|
||||
pdfiumWasm.HEAPU8.buffer.slice(heapPtr, heapPtr + bytes),
|
||||
pdfiumRuntime.HEAPU8.subarray(heapPtr, heapPtr + bytes),
|
||||
);
|
||||
let hasVisible = false;
|
||||
for (let i = 3; i < rgba.length; i += 4) {
|
||||
@@ -1417,7 +1481,7 @@ async function renderWidgetAppearance(
|
||||
m.FPDFBitmap_Destroy(bmp2);
|
||||
|
||||
const rgba2 = new Uint8ClampedArray(
|
||||
pdfiumWasm.HEAPU8.buffer.slice(heap2, heap2 + bytes),
|
||||
pdfiumRuntime.HEAPU8.subarray(heap2, heap2 + bytes),
|
||||
);
|
||||
let hasVisible2 = false;
|
||||
for (let i = 3; i < rgba2.length; i += 4) {
|
||||
@@ -1560,8 +1624,6 @@ export async function renderSignatureFieldAppearances(
|
||||
const hDev = Math.max(1, Math.round(pdfH * dpr));
|
||||
const stride = wDev * 4;
|
||||
const bytes = stride * hDev;
|
||||
const pdfiumWasm = m.pdfium as any;
|
||||
|
||||
const heapPtr = m.pdfium.wasmExports.malloc(bytes);
|
||||
const bitmapPtr = m.FPDFBitmap_CreateEx(
|
||||
wDev,
|
||||
@@ -1577,8 +1639,10 @@ export async function renderSignatureFieldAppearances(
|
||||
const sx = wDev / pdfW;
|
||||
const sy = hDev / pdfH;
|
||||
const matrixPtr = m.pdfium.wasmExports.malloc(6 * 4);
|
||||
const pdfiumRuntime = m.pdfium as typeof m.pdfium &
|
||||
ExtendedPdfiumRuntime;
|
||||
const matrixView = new Float32Array(
|
||||
pdfiumWasm.HEAPF32.buffer,
|
||||
pdfiumRuntime.HEAPF32.buffer,
|
||||
matrixPtr,
|
||||
6,
|
||||
);
|
||||
@@ -1603,7 +1667,7 @@ export async function renderSignatureFieldAppearances(
|
||||
|
||||
if (ok) {
|
||||
const rgba = new Uint8ClampedArray(
|
||||
pdfiumWasm.HEAPU8.buffer.slice(heapPtr, heapPtr + bytes),
|
||||
pdfiumRuntime.HEAPU8.subarray(heapPtr, heapPtr + bytes),
|
||||
);
|
||||
let hasVisible = false;
|
||||
for (let i = 3; i < rgba.length; i += 4) {
|
||||
@@ -1663,7 +1727,7 @@ export async function renderSignatureFieldAppearances(
|
||||
m.FPDFBitmap_Destroy(bmp2);
|
||||
|
||||
const rgba2 = new Uint8ClampedArray(
|
||||
pdfiumWasm.HEAPU8.buffer.slice(heap2, heap2 + bytes),
|
||||
pdfiumRuntime.HEAPU8.subarray(heap2, heap2 + bytes),
|
||||
);
|
||||
let hasVisible2 = false;
|
||||
for (let i = 3; i < rgba2.length; i += 4) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { BASE_PATH } from "@app/constants/app";
|
||||
import pdfiumWasmAssetUrl from "@embedpdf/pdfium/pdfium.wasm?url";
|
||||
|
||||
const getWasmUrl = (): string => {
|
||||
if (
|
||||
pdfiumWasmAssetUrl.startsWith("http://") ||
|
||||
pdfiumWasmAssetUrl.startsWith("https://") ||
|
||||
pdfiumWasmAssetUrl.startsWith("//")
|
||||
) {
|
||||
return pdfiumWasmAssetUrl;
|
||||
}
|
||||
|
||||
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
||||
if (import.meta.env.DEV) {
|
||||
return `${origin}${BASE_PATH}/pdfium/pdfium.wasm`;
|
||||
}
|
||||
const cleanAssetUrl = pdfiumWasmAssetUrl
|
||||
.replace(/^\.\//, "")
|
||||
.replace(/^\//, "");
|
||||
return `${origin}${BASE_PATH}/${cleanAssetUrl}`;
|
||||
};
|
||||
|
||||
export const pdfiumWasmUrl = getWasmUrl();
|
||||
|
||||
let resolvePromise: (module: WebAssembly.Module | null) => void;
|
||||
let compilationStarted = false;
|
||||
|
||||
export const pdfiumWasmModulePromise = new Promise<WebAssembly.Module | null>(
|
||||
(resolve) => {
|
||||
resolvePromise = resolve;
|
||||
},
|
||||
);
|
||||
|
||||
export function startEagerWasmCompilation(): void {
|
||||
if (compilationStarted) return;
|
||||
compilationStarted = true;
|
||||
|
||||
if (
|
||||
typeof WebAssembly === "object" &&
|
||||
typeof WebAssembly.compileStreaming === "function"
|
||||
) {
|
||||
WebAssembly.compileStreaming(fetch(pdfiumWasmUrl))
|
||||
.then(resolvePromise)
|
||||
.catch((err) => {
|
||||
console.warn(
|
||||
"Eager WASM compilation failed or not supported in this environment:",
|
||||
err,
|
||||
);
|
||||
resolvePromise(null);
|
||||
});
|
||||
} else {
|
||||
resolvePromise(null);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,24 @@ import posthog from "posthog-js";
|
||||
import { PostHogProvider } from "@posthog/react";
|
||||
import { BASE_PATH } from "@app/constants/app";
|
||||
|
||||
import { startEagerWasmCompilation } from "@app/services/wasmPrecompiler";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const scheduleCompilation = () => {
|
||||
if (typeof requestIdleCallback === "function") {
|
||||
requestIdleCallback(() => startEagerWasmCompilation(), { timeout: 2000 });
|
||||
} else {
|
||||
setTimeout(startEagerWasmCompilation, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === "complete") {
|
||||
scheduleCompilation();
|
||||
} else {
|
||||
window.addEventListener("load", scheduleCompilation);
|
||||
}
|
||||
}
|
||||
|
||||
posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
|
||||
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
|
||||
defaults: "2025-05-24",
|
||||
|
||||
Reference in New Issue
Block a user