mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
feat(viewer): add self-hosted PDFium font fallback and eager WASM streaming fallback
This commit is contained in:
@@ -100,6 +100,7 @@ import { DocumentPermissionsAPIBridge } from "@app/components/viewer/DocumentPer
|
||||
import { DocumentReadyWrapper } from "@app/components/viewer/DocumentReadyWrapper";
|
||||
import { ActiveDocumentProvider } from "@app/components/viewer/ActiveDocumentContext";
|
||||
import { pdfiumWasmUrl } from "@app/services/wasmPrecompiler";
|
||||
import { getLocalFontFallbackConfig } from "@app/services/pdfiumFontFallback";
|
||||
import { FormFieldOverlay } from "@app/tools/formFill/FormFieldOverlay";
|
||||
import { FormCreationInteractionLock } from "@app/tools/formFill/FormCreationInteractionLock";
|
||||
import { FormFieldCreationOverlay } from "@app/tools/formFill/FormFieldCreationOverlay";
|
||||
@@ -437,9 +438,12 @@ export function LocalEmbedPDF({
|
||||
];
|
||||
}, [pdfUrl, enableAnnotations, exportFileName]);
|
||||
|
||||
const fontFallbackConfig = useMemo(() => getLocalFontFallbackConfig(), []);
|
||||
|
||||
// Initialize the engine with the React hook - use local WASM for offline support
|
||||
const { engine, isLoading, error } = usePdfiumEngine({
|
||||
wasmUrl: pdfiumWasmUrl,
|
||||
fontFallback: fontFallbackConfig,
|
||||
});
|
||||
|
||||
// Early return if no file or URL provided
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { FontCharset } from "@embedpdf/models";
|
||||
import { getLocalFontFallbackConfig } from "@app/services/pdfiumFontFallback";
|
||||
|
||||
describe("pdfiumFontFallback", () => {
|
||||
it("generates a self-hosted font fallback configuration without external CDN URLs", () => {
|
||||
const config = getLocalFontFallbackConfig();
|
||||
|
||||
expect(config.baseUrl).toContain("/fonts");
|
||||
expect(config.defaultFont).toBe("NotoSans-Regular.ttf");
|
||||
|
||||
expect(config.fonts[FontCharset.ANSI]).toBe("NotoSans-Regular.ttf");
|
||||
expect(config.fonts[FontCharset.DEFAULT]).toBe("NotoSans-Regular.ttf");
|
||||
expect(config.fonts[FontCharset.SHIFTJIS]).toBe("NotoSansJP-Regular.ttf");
|
||||
expect(config.fonts[FontCharset.HANGEUL]).toBe("NotoSansKR-Regular.ttf");
|
||||
expect(config.fonts[FontCharset.GB2312]).toBe("NotoSansSC-Regular.ttf");
|
||||
expect(config.fonts[FontCharset.CHINESEBIG5]).toBe(
|
||||
"NotoSansTC-Regular.ttf",
|
||||
);
|
||||
expect(config.fonts[FontCharset.ARABIC]).toBe("NotoSansArabic-Regular.ttf");
|
||||
expect(config.fonts[FontCharset.THAI]).toBe("NotoSansThai-Regular.ttf");
|
||||
|
||||
expect(config.baseUrl).not.toContain("jsdelivr");
|
||||
for (const fontVal of Object.values(config.fonts)) {
|
||||
expect(String(fontVal)).not.toContain("http://");
|
||||
expect(String(fontVal)).not.toContain("https://");
|
||||
expect(String(fontVal)).not.toContain("jsdelivr");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { BASE_PATH } from "@app/constants/app";
|
||||
import type { FontFallbackConfig } from "@embedpdf/engines";
|
||||
import { FontCharset } from "@embedpdf/models";
|
||||
|
||||
export function getLocalFontFallbackConfig(): FontFallbackConfig {
|
||||
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
||||
const baseUrl = `${origin}${BASE_PATH}/fonts`;
|
||||
|
||||
return {
|
||||
baseUrl,
|
||||
defaultFont: "NotoSans-Regular.ttf",
|
||||
fonts: {
|
||||
[FontCharset.ANSI]: "NotoSans-Regular.ttf",
|
||||
[FontCharset.DEFAULT]: "NotoSans-Regular.ttf",
|
||||
[FontCharset.CYRILLIC]: "NotoSans-Regular.ttf",
|
||||
[FontCharset.GREEK]: "NotoSans-Regular.ttf",
|
||||
[FontCharset.VIETNAMESE]: "NotoSans-Regular.ttf",
|
||||
[FontCharset.EASTERNEUROPEAN]: "NotoSans-Regular.ttf",
|
||||
[FontCharset.ARABIC]: "NotoSansArabic-Regular.ttf",
|
||||
[FontCharset.THAI]: "NotoSansThai-Regular.ttf",
|
||||
[FontCharset.SHIFTJIS]: "NotoSansJP-Regular.ttf",
|
||||
[FontCharset.HANGEUL]: "NotoSansKR-Regular.ttf",
|
||||
[FontCharset.GB2312]: "NotoSansSC-Regular.ttf",
|
||||
[FontCharset.CHINESEBIG5]: "NotoSansTC-Regular.ttf",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -32,20 +32,40 @@ 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 {
|
||||
if (typeof WebAssembly !== "object") {
|
||||
resolvePromise(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const compileWithFallback = async (): Promise<WebAssembly.Module | null> => {
|
||||
try {
|
||||
if (typeof WebAssembly.compileStreaming === "function") {
|
||||
try {
|
||||
return await WebAssembly.compileStreaming(fetch(pdfiumWasmUrl));
|
||||
} catch (streamingErr) {
|
||||
console.warn(
|
||||
"WASM compileStreaming failed, falling back to ArrayBuffer:",
|
||||
streamingErr,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// compileStreaming requires application/wasm MIME; fall back to ArrayBuffer if the server or proxy serves octet-stream.
|
||||
const res = await fetch(pdfiumWasmUrl);
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch WASM: ${res.status} ${res.statusText}`,
|
||||
);
|
||||
}
|
||||
const buffer = await res.arrayBuffer();
|
||||
return await WebAssembly.compile(buffer);
|
||||
} catch (err) {
|
||||
console.warn("WASM compilation failed:", err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
compileWithFallback()
|
||||
.then(resolvePromise)
|
||||
.catch(() => resolvePromise(null));
|
||||
}
|
||||
|
||||
@@ -256,6 +256,7 @@ export default defineConfig(async ({ mode, command }) => {
|
||||
"/login/saml2": backendProxy,
|
||||
"/swagger-ui": backendProxy,
|
||||
"/v1/api-docs": backendProxy,
|
||||
"/fonts": backendProxy,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -340,6 +341,11 @@ export default defineConfig(async ({ mode, command }) => {
|
||||
src: "src/core/assets/brand/modern-logo/*",
|
||||
dest: "modern-logo",
|
||||
},
|
||||
{
|
||||
// Fallback TrueType fonts for PDFium (Noto Sans, CJK, Arabic, etc.)
|
||||
src: "../../app/core/src/main/resources/static/fonts/*.ttf",
|
||||
dest: "fonts",
|
||||
},
|
||||
],
|
||||
}),
|
||||
compressStaticCopyPlugin(),
|
||||
|
||||
Reference in New Issue
Block a user