mozilla/pdf.js
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
"name": "bizmatch-qc-desktop",
|
"name": "bizmatch-qc-desktop",
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"exports": "./main.ts",
|
"exports": "./main.ts",
|
||||||
|
"unstable": ["raw-imports"],
|
||||||
"imports": {
|
"imports": {
|
||||||
"@std/assert": "jsr:@std/assert@^1",
|
"@std/assert": "jsr:@std/assert@^1",
|
||||||
"@std/path": "jsr:@std/path@^1",
|
"@std/path": "jsr:@std/path@^1",
|
||||||
|
|||||||
154
main.ts
154
main.ts
@@ -25,6 +25,147 @@ import PDFJS_LIB_TEXT from "pdfjs-dist/legacy/build/pdf.min.mjs" with {
|
|||||||
import PDFJS_WORKER_TEXT from "pdfjs-dist/legacy/build/pdf.worker.min.mjs" with {
|
import PDFJS_WORKER_TEXT from "pdfjs-dist/legacy/build/pdf.worker.min.mjs" with {
|
||||||
type: "text",
|
type: "text",
|
||||||
};
|
};
|
||||||
|
// PDF.js >= 5 needs these auxiliary assets at runtime (fetched by the worker):
|
||||||
|
// - wasm/: CCITT-G4 + JBIG2 (B/W scans), JPEG2000 and qcms (ICC) decoders
|
||||||
|
// - standard_fonts/: base-14 font substitutes for PDFs without embedded fonts
|
||||||
|
// - iccs/: default CMYK ICC profile
|
||||||
|
// Embedded via import attributes so the compiled desktop binary stays self-contained.
|
||||||
|
import WASM_JBIG2 from "pdfjs-dist/wasm/jbig2.wasm" with { type: "bytes" };
|
||||||
|
import WASM_OPENJPEG from "pdfjs-dist/wasm/openjpeg.wasm" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import WASM_QCMS from "pdfjs-dist/wasm/qcms_bg.wasm" with { type: "bytes" };
|
||||||
|
import JS_JBIG2_FALLBACK from "pdfjs-dist/wasm/jbig2_nowasm_fallback.js" with {
|
||||||
|
type: "text",
|
||||||
|
};
|
||||||
|
import JS_OPENJPEG_FALLBACK from "pdfjs-dist/wasm/openjpeg_nowasm_fallback.js" with {
|
||||||
|
type: "text",
|
||||||
|
};
|
||||||
|
import ICC_CGATS from "pdfjs-dist/iccs/CGATS001Compat-v2-micro.icc" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_DINGBATS from "pdfjs-dist/standard_fonts/FoxitDingbats.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_FIXED from "pdfjs-dist/standard_fonts/FoxitFixed.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_FIXED_B from "pdfjs-dist/standard_fonts/FoxitFixedBold.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_FIXED_BI from "pdfjs-dist/standard_fonts/FoxitFixedBoldItalic.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_FIXED_I from "pdfjs-dist/standard_fonts/FoxitFixedItalic.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_SERIF from "pdfjs-dist/standard_fonts/FoxitSerif.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_SERIF_B from "pdfjs-dist/standard_fonts/FoxitSerifBold.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_SERIF_BI from "pdfjs-dist/standard_fonts/FoxitSerifBoldItalic.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_SERIF_I from "pdfjs-dist/standard_fonts/FoxitSerifItalic.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_SYMBOL from "pdfjs-dist/standard_fonts/FoxitSymbol.pfb" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_LSANS from "pdfjs-dist/standard_fonts/LiberationSans-Regular.ttf" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_LSANS_B from "pdfjs-dist/standard_fonts/LiberationSans-Bold.ttf" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_LSANS_BI from "pdfjs-dist/standard_fonts/LiberationSans-BoldItalic.ttf" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
import FONT_LSANS_I from "pdfjs-dist/standard_fonts/LiberationSans-Italic.ttf" with {
|
||||||
|
type: "bytes",
|
||||||
|
};
|
||||||
|
|
||||||
|
const PDFJS_ASSETS: Record<
|
||||||
|
string,
|
||||||
|
{ body: Uint8Array | string; type: string }
|
||||||
|
> = {
|
||||||
|
"/pdfjs/wasm/jbig2.wasm": { body: WASM_JBIG2, type: "application/wasm" },
|
||||||
|
"/pdfjs/wasm/openjpeg.wasm": {
|
||||||
|
body: WASM_OPENJPEG,
|
||||||
|
type: "application/wasm",
|
||||||
|
},
|
||||||
|
"/pdfjs/wasm/qcms_bg.wasm": { body: WASM_QCMS, type: "application/wasm" },
|
||||||
|
"/pdfjs/wasm/jbig2_nowasm_fallback.js": {
|
||||||
|
body: JS_JBIG2_FALLBACK,
|
||||||
|
type: "text/javascript; charset=utf-8",
|
||||||
|
},
|
||||||
|
"/pdfjs/wasm/openjpeg_nowasm_fallback.js": {
|
||||||
|
body: JS_OPENJPEG_FALLBACK,
|
||||||
|
type: "text/javascript; charset=utf-8",
|
||||||
|
},
|
||||||
|
"/pdfjs/iccs/CGATS001Compat-v2-micro.icc": {
|
||||||
|
body: ICC_CGATS,
|
||||||
|
type: "application/vnd.iccprofile",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitDingbats.pfb": {
|
||||||
|
body: FONT_DINGBATS,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitFixed.pfb": {
|
||||||
|
body: FONT_FIXED,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitFixedBold.pfb": {
|
||||||
|
body: FONT_FIXED_B,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb": {
|
||||||
|
body: FONT_FIXED_BI,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitFixedItalic.pfb": {
|
||||||
|
body: FONT_FIXED_I,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitSerif.pfb": {
|
||||||
|
body: FONT_SERIF,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitSerifBold.pfb": {
|
||||||
|
body: FONT_SERIF_B,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb": {
|
||||||
|
body: FONT_SERIF_BI,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitSerifItalic.pfb": {
|
||||||
|
body: FONT_SERIF_I,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/FoxitSymbol.pfb": {
|
||||||
|
body: FONT_SYMBOL,
|
||||||
|
type: "application/octet-stream",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/LiberationSans-Regular.ttf": {
|
||||||
|
body: FONT_LSANS,
|
||||||
|
type: "font/ttf",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/LiberationSans-Bold.ttf": {
|
||||||
|
body: FONT_LSANS_B,
|
||||||
|
type: "font/ttf",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf": {
|
||||||
|
body: FONT_LSANS_BI,
|
||||||
|
type: "font/ttf",
|
||||||
|
},
|
||||||
|
"/pdfjs/standard_fonts/LiberationSans-Italic.ttf": {
|
||||||
|
body: FONT_LSANS_I,
|
||||||
|
type: "font/ttf",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const PREFIX = "[BizMatch QC]";
|
const PREFIX = "[BizMatch QC]";
|
||||||
|
|
||||||
@@ -452,6 +593,17 @@ Deno.serve(async (request) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- PDF.js auxiliary assets (wasm decoders, standard fonts, ICC) ----
|
||||||
|
const pdfjsAsset = PDFJS_ASSETS[url.pathname];
|
||||||
|
if (pdfjsAsset) {
|
||||||
|
return new Response(pdfjsAsset.body as BodyInit, {
|
||||||
|
headers: {
|
||||||
|
"content-type": pdfjsAsset.type,
|
||||||
|
"cache-control": "public, max-age=86400",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---- PDF.js library assets ----
|
// ---- PDF.js library assets ----
|
||||||
if (url.pathname === "/pdfjs/legacy/pdf.min.mjs") {
|
if (url.pathname === "/pdfjs/legacy/pdf.min.mjs") {
|
||||||
return new Response(PDFJS_LIB_TEXT, {
|
return new Response(PDFJS_LIB_TEXT, {
|
||||||
@@ -613,4 +765,4 @@ try {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
win.show();
|
win.show();
|
||||||
13
web/app.js
13
web/app.js
@@ -1,6 +1,7 @@
|
|||||||
let state;
|
let state;
|
||||||
let groups = [];
|
let groups = [];
|
||||||
let selectedIndex = -1;
|
let selectedIndex = -1;
|
||||||
|
let loadedIndex = -1; // which document the PDF viewer currently shows/loads
|
||||||
let pdfViewer = null;
|
let pdfViewer = null;
|
||||||
let loadRequestId = 0;
|
let loadRequestId = 0;
|
||||||
|
|
||||||
@@ -116,10 +117,14 @@ async function loadPdf(index) {
|
|||||||
const doc = state.documents[index];
|
const doc = state.documents[index];
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
|
|
||||||
// Deduplicate: don't reload the same document
|
// Deduplicate: don't reload the document that is already shown.
|
||||||
if (index === selectedIndex && pdfViewer) {
|
// NOTE: must compare against loadedIndex, NOT selectedIndex --
|
||||||
|
// select() updates selectedIndex before calling loadPdf(), so a
|
||||||
|
// selectedIndex comparison is always true and blocks every reload.
|
||||||
|
if (index === loadedIndex && pdfViewer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
loadedIndex = index;
|
||||||
|
|
||||||
if (pdfViewer) {
|
if (pdfViewer) {
|
||||||
pdfViewer.destroy();
|
pdfViewer.destroy();
|
||||||
@@ -164,6 +169,7 @@ async function loadPdf(index) {
|
|||||||
console.log(`[BizMatch QC] PDF ${reqId} rendering complete`);
|
console.log(`[BizMatch QC] PDF ${reqId} rendering complete`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (index !== selectedIndex) return;
|
if (index !== selectedIndex) return;
|
||||||
|
loadedIndex = -1; // allow retry: clicking the same document again reloads it
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
pdfMessage.textContent = `Cannot open PDF: ${message}`;
|
pdfMessage.textContent = `Cannot open PDF: ${message}`;
|
||||||
pdfMessage.hidden = false;
|
pdfMessage.hidden = false;
|
||||||
@@ -281,6 +287,7 @@ async function load() {
|
|||||||
throw new Error(`State request failed with HTTP ${response.status}.`);
|
throw new Error(`State request failed with HTTP ${response.status}.`);
|
||||||
}
|
}
|
||||||
state = await response.json();
|
state = await response.json();
|
||||||
|
loadedIndex = -1; // document set changed; index-based dedup is invalid now
|
||||||
groups = buildGroups(state.documents);
|
groups = buildGroups(state.documents);
|
||||||
showError(state.loadError || "");
|
showError(state.loadError || "");
|
||||||
renderList();
|
renderList();
|
||||||
@@ -290,4 +297,4 @@ async function load() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
@@ -71,6 +71,13 @@ class PdfViewer {
|
|||||||
url,
|
url,
|
||||||
rangeChunkSize: 65536,
|
rangeChunkSize: 65536,
|
||||||
disableAutoFetch: false,
|
disableAutoFetch: false,
|
||||||
|
// PDF.js >= 5 decodes CCITT-G4/JBIG2 (B/W scans), JPEG2000 and ICC
|
||||||
|
// color via WASM modules fetched from wasmUrl. Without these URLs
|
||||||
|
// the decoders fail silently (ignoreErrors default) and scanned
|
||||||
|
// pages render as blank white canvases of the correct size.
|
||||||
|
wasmUrl: "/pdfjs/wasm/",
|
||||||
|
standardFontDataUrl: "/pdfjs/standard_fonts/",
|
||||||
|
iccUrl: "/pdfjs/iccs/",
|
||||||
});
|
});
|
||||||
this.loadingTask = task;
|
this.loadingTask = task;
|
||||||
|
|
||||||
@@ -259,4 +266,4 @@ function errorMsg(err) {
|
|||||||
return err instanceof Error ? err.message : String(err);
|
return err instanceof Error ? err.message : String(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.PdfViewer = PdfViewer;
|
globalThis.PdfViewer = PdfViewer;
|
||||||
Reference in New Issue
Block a user