@file-viewer/renderer-pdf 2.1.25 → 2.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -25,21 +25,28 @@ const options = {
25
25
 
26
26
  ## Offline Assets
27
27
 
28
- PDF preview depends on the PDF.js worker, cMaps, WASM helpers, and standard fonts. Asset paths use the same unified options as `@file-viewer/core`:
28
+ PDF preview depends on the PDF.js worker, cMaps, WASM helpers, standard fonts, and an offline fallback for unembedded CJK fonts. Asset paths use the same unified options as `@file-viewer/core`:
29
29
 
30
30
  ```ts
31
31
  const options = {
32
32
  renderers: pdfRenderer,
33
33
  pdf: {
34
+ assetBaseUrl: '/workspace/',
34
35
  workerUrl: '/vendor/pdf/pdf.worker.mjs',
35
36
  cMapUrl: '/vendor/pdf/cmaps/',
36
37
  wasmUrl: '/vendor/pdf/wasm/',
37
38
  standardFontDataUrl: '/vendor/pdf/standard_fonts/',
39
+ cjkFontFallbackPath: '/vendor/pdf/fonts/',
40
+ identityFontRepair: true,
38
41
  },
39
42
  }
40
43
  ```
41
44
 
42
- When no explicit URL is provided, the renderer resolves `vendor/pdf/pdf.worker.mjs` against the current document `baseURI`. Apps deployed under a subpath such as `/workspace/` therefore request `/workspace/vendor/pdf/pdf.worker.mjs` instead of forcing the site root. If the host app did not run `file-viewer-copy-assets`, does not use `@file-viewer/vite-plugin`, or a local dev server falls back to HTML for that path, the renderer lazy-loads the packaged PDF.js worker handler as a compatibility fallback so preview does not fail with `Setting up fake worker failed`. For best performance, full cMap/standard-font support, or strict offline deployments, still copy viewer assets and point these URLs at real static files; `wasmUrl` remains for PDF.js builds that ship WASM assets.
45
+ When no explicit PDF asset URL is configured, the renderer first uses the public base exposed by build tools such as Vite and can fall back to the page entry script. An app deployed under `/workspace/` therefore still requests `/workspace/vendor/pdf/pdf.worker.mjs` while its current SPA route is `/workspace/c/`. UMI, custom proxies, and builds whose public path cannot be detected can set `pdf.assetBaseUrl: '/workspace/'` to pin the base for every PDF offline asset. If the host app did not run `file-viewer-copy-assets`, does not use `@file-viewer/vite-plugin`, or a local dev server falls back to HTML for that path, the renderer lazy-loads the packaged PDF.js worker handler as a compatibility fallback so preview does not fail with `Setting up fake worker failed`. For best performance, complete cMap/standard-font/WASM decoding, or strict offline deployments, still copy viewer assets and point these URLs at real static files. The pinned PDF.js 5.4 stable line loads local JBIG2, JPEG 2000, color-management, and related helpers through `wasmUrl`; viewer assets and the Vite plugin copy them without relying on the public internet.
46
+
47
+ `cjkFontFallback` is enabled by default. When a PDF references `MicrosoftYaHei-Bold`, SimSun, SimHei, or another CJK font without embedding the font program, the renderer prefers an installed system font and otherwise aliases the original PDF font name to the bundled Noto Sans SC variable font. Only WOFF2 shards needed by the current page text are loaded. The first page waits for the font before rendering, and later pages are redrawn when they introduce new glyphs. Set the option to `false` to disable this behavior, or point `cjkFontFallbackPath` at a self-hosted directory with the same structure.
48
+
49
+ `identityFontRepair` is also enabled by default for malformed PDFs that write TrueType glyph IDs through `Identity-H`/`Identity-V` but omit `ToUnicode`. The repair module is loaded only after CJK font substitution and multiple control-character glyphs are detected. If the same PDF embeds a usable same-family TrueType font, its cmap is used to rebuild the Unicode mapping for the in-memory preview. The repair is applied only to a preview copy and never overwrites the original file or download source; an unsafe or unsupported repair falls back to the original preview. Set the option to `false` to disable this compatibility path.
43
50
 
44
51
  ## Migration Note
45
52
 
package/README.md CHANGED
@@ -25,21 +25,28 @@ const options = {
25
25
 
26
26
  ## 离线资源
27
27
 
28
- PDF 预览依赖 PDF.js worker、cMaps、WASMstandard fonts。资源路径沿用 `@file-viewer/core` 的统一 options:
28
+ PDF 预览依赖 PDF.js worker、cMaps、WASMstandard fonts,以及未嵌入中文字体的离线回退资源。资源路径沿用 `@file-viewer/core` 的统一 options:
29
29
 
30
30
  ```ts
31
31
  const options = {
32
32
  renderers: pdfRenderer,
33
33
  pdf: {
34
+ assetBaseUrl: '/workspace/',
34
35
  workerUrl: '/vendor/pdf/pdf.worker.mjs',
35
36
  cMapUrl: '/vendor/pdf/cmaps/',
36
37
  wasmUrl: '/vendor/pdf/wasm/',
37
38
  standardFontDataUrl: '/vendor/pdf/standard_fonts/',
39
+ cjkFontFallbackPath: '/vendor/pdf/fonts/',
40
+ identityFontRepair: true,
38
41
  },
39
42
  }
40
43
  ```
41
44
 
42
- 默认未显式配置时,渲染器会基于当前文档 `baseURI` 探测 `vendor/pdf/pdf.worker.mjs`,因此 Vite/UMI 等项目部署在 `/workspace/` 这类子路径时会请求 `/workspace/vendor/pdf/pdf.worker.mjs`,不会强制落到站点根路径。如果客户项目没有执行 `file-viewer-copy-assets`、没有使用 `@file-viewer/vite-plugin`,或者本地临时服务器把该路径回退成 HTML,PDF renderer 会自动懒加载包内 PDF.js worker handler 作为兼容兜底,避免 `Setting up fake worker failed` 直接中断预览。需要最佳性能、完整 cMap/standard fonts 或严格离线部署时,仍建议复制 viewer assets 并配置真实静态地址;`wasmUrl` 保留给提供 WASM 资产的 PDF.js 构建兼容使用。
45
+ 默认未显式配置时,渲染器优先使用 Vite 等构建器提供的公开基址,并可从页面入口脚本回退识别部署子路径。因此应用部署在 `/workspace/`、当前 SPA 路由为 `/workspace/c/` 时,仍会请求 `/workspace/vendor/pdf/pdf.worker.mjs`。UMI、自定义代理或无法自动识别的构建环境可以设置 `pdf.assetBaseUrl: '/workspace/'` 固定全部 PDF 离线资源的基址。如果项目没有执行 `file-viewer-copy-assets`、没有使用 `@file-viewer/vite-plugin`,或者本地临时服务器把该路径回退成 HTML,PDF renderer 会自动懒加载包内 PDF.js worker handler 作为兼容兜底,避免 `Setting up fake worker failed` 直接中断预览。需要最佳性能、完整 cMap/standard fonts/WASM 解码或严格离线部署时,仍建议复制 viewer assets 并配置真实静态地址。当前锁定的 PDF.js 5.4 稳定线会通过 `wasmUrl` 本地加载 JBIG2、JPEG 2000、颜色管理等辅助资源;这些资源由 viewer assets 和 Vite 插件统一复制,不依赖公网。
46
+
47
+ `cjkFontFallback` 默认开启。PDF 引用了 `MicrosoftYaHei-Bold`、宋体、黑体或其他中文字体但没有嵌入字体数据时,渲染器优先使用本机字体;本机缺失时,会把原 PDF 字体名映射到本地 Noto Sans SC 可变字体,并根据当前页实际文字只加载需要的 WOFF2 分片。首屏会在字体就绪后再渲染,后续页发现新字形时会自动重绘。可设为 `false` 完全关闭,或用 `cjkFontFallbackPath` 指向同结构的自托管字体目录。
48
+
49
+ `identityFontRepair` 也默认开启,用于少数把 TrueType 字形 ID 写入 `Identity-H`/`Identity-V`、却遗漏 `ToUnicode` 的异常 PDF。只有检测到中文字体替换和多个控制字符乱码时,渲染器才会懒加载修复模块;若文件内同时嵌入了可用的同族 TrueType 字体,就从其 cmap 重建内存预览所需的 Unicode 映射。修复只作用于预览副本,不覆盖原始文件和下载源;无法安全修复时继续使用原预览。可设为 `false` 关闭。
43
50
 
44
51
  ## 迁移说明
45
52
 
package/dist/pdf.js CHANGED
@@ -3,6 +3,7 @@ import { EventBus, GenericL10n, PDFFindController, PDFLinkService, PDFViewer, }
3
3
  import { registerFileViewerSearchProvider, registerFileViewerZoomProvider, unregisterFileViewerSearchProvider, unregisterFileViewerZoomProvider, registerFileViewerViewStateProvider, unregisterFileViewerViewStateProvider, createFileViewerZoomChangeEmitter, createFileViewerViewStateChange, createFileViewerViewStateChangeEmitter, createFileViewerTranslator, buildPrintPageStyle, formatCssPixels, DEFAULT_PDF_RANGE_CHUNK_SIZE, resolveFileViewerLocale, resolveFileViewerFitScale, } from '@file-viewer/core';
4
4
  import { DEFAULT_FILE_VIEWER_PDF_WORKER_PATH, resolveFileViewerPdfAssetUrls, } from '@file-viewer/core/assets';
5
5
  import { pdfViewerStyle } from './pdfStyles.js';
6
+ import { collectMalformedIdentityFontNames, createPdfCjkFontFallbackManager, detectMalformedIdentityCjkFontFamilies, } from './pdfFontFallback.js';
6
7
  export const DEFAULT_FILE_VIEWER_PDF_WORKER_URL = DEFAULT_FILE_VIEWER_PDF_WORKER_PATH;
7
8
  const MIN_SCALE = 0.2;
8
9
  const MAX_SCALE = 3;
@@ -19,6 +20,7 @@ const normalizedPdfViewerStyle = pdfViewerStyle
19
20
  .replace(/--page-border-image:\s*url\(images\/shadow\.png\)\s*9 9 repeat;/g, '--page-border-image:none;')
20
21
  .replace(/background:\s*url\("\.\/images\/loading-icon\.gif"\)\s*center no-repeat;/g, 'background:none;');
21
22
  const pdfJsConsoleErrorSuppressions = new WeakMap();
23
+ const pdfJsConsoleWarningSuppressions = new WeakMap();
22
24
  const createStyle = (documentRef) => {
23
25
  const style = documentRef.createElement('style');
24
26
  style.textContent = `${normalizedPdfViewerStyle}
@@ -164,6 +166,63 @@ const suppressPdfJsDestroyedTransportPageInitErrors = (view) => {
164
166
  }, PDF_JS_DESTROY_CONSOLE_SUPPRESSION_MS);
165
167
  };
166
168
  };
169
+ const isPdfJsMissingSystemFontWarning = (args) => {
170
+ const [message] = args;
171
+ return typeof message === 'string' &&
172
+ /^(?:Warning:\s*)?Cannot load system font: .+installing it could help to improve PDF rendering\.$/.test(message);
173
+ };
174
+ const suppressPdfJsMissingSystemFontWarnings = (view) => {
175
+ const consoleRef = (view.console ||
176
+ globalThis.console);
177
+ if (!consoleRef || typeof consoleRef.warn !== 'function') {
178
+ return () => { };
179
+ }
180
+ let suppression = pdfJsConsoleWarningSuppressions.get(consoleRef);
181
+ if (!suppression) {
182
+ const originalWarn = consoleRef.warn;
183
+ suppression = {
184
+ originalWarn,
185
+ patchedWarn: (...args) => {
186
+ if (isPdfJsMissingSystemFontWarning(args)) {
187
+ return;
188
+ }
189
+ return originalWarn.apply(consoleRef, args);
190
+ },
191
+ depth: 0,
192
+ restoreTimer: undefined,
193
+ };
194
+ pdfJsConsoleWarningSuppressions.set(consoleRef, suppression);
195
+ consoleRef.warn = suppression.patchedWarn;
196
+ }
197
+ else if (suppression.restoreTimer !== undefined) {
198
+ view.clearTimeout(suppression.restoreTimer);
199
+ suppression.restoreTimer = undefined;
200
+ }
201
+ suppression.depth += 1;
202
+ let released = false;
203
+ return () => {
204
+ if (released) {
205
+ return;
206
+ }
207
+ released = true;
208
+ const current = pdfJsConsoleWarningSuppressions.get(consoleRef);
209
+ if (!current) {
210
+ return;
211
+ }
212
+ current.depth = Math.max(0, current.depth - 1);
213
+ if (current.depth || current.restoreTimer !== undefined) {
214
+ return;
215
+ }
216
+ current.restoreTimer = view.setTimeout(() => {
217
+ current.restoreTimer = undefined;
218
+ if (current.depth || consoleRef.warn !== current.patchedWarn) {
219
+ return;
220
+ }
221
+ consoleRef.warn = current.originalWarn;
222
+ pdfJsConsoleWarningSuppressions.delete(consoleRef);
223
+ }, PDF_JS_DESTROY_CONSOLE_SUPPRESSION_MS);
224
+ };
225
+ };
167
226
  const isConfiguredUrl = (value) => {
168
227
  return value !== undefined && value !== null && String(value).trim().length > 0;
169
228
  };
@@ -195,6 +254,50 @@ const installBundledPdfFakeWorker = async () => {
195
254
  const resolvePdfWorkerUrl = (options, documentBaseUrl) => {
196
255
  return resolveFileViewerPdfAssetUrls(options, documentBaseUrl).workerUrl;
197
256
  };
257
+ const readBundlerPublicBaseUrl = () => {
258
+ var _a;
259
+ try {
260
+ const value = (_a = import.meta.env) === null || _a === void 0 ? void 0 : _a.BASE_URL;
261
+ return typeof value === 'string' ? value.trim() : '';
262
+ }
263
+ catch {
264
+ return '';
265
+ }
266
+ };
267
+ const resolvePdfRuntimeAssetBaseUrl = (documentRef) => {
268
+ const documentBaseUrl = documentRef.baseURI || documentRef.URL || 'file:///';
269
+ const bundlerBaseUrl = readBundlerPublicBaseUrl();
270
+ if (bundlerBaseUrl && bundlerBaseUrl !== '.' && bundlerBaseUrl !== './') {
271
+ try {
272
+ return new URL(bundlerBaseUrl.endsWith('/') ? bundlerBaseUrl : `${bundlerBaseUrl}/`, documentBaseUrl).href;
273
+ }
274
+ catch {
275
+ // Continue with DOM-based public-path detection.
276
+ }
277
+ }
278
+ // An explicit <base> is authoritative and already reflected by baseURI.
279
+ if (documentRef.querySelector('base[href]')) {
280
+ return documentBaseUrl;
281
+ }
282
+ // Entry scripts remain under the public deployment base when an SPA route
283
+ // changes document.baseURI. This covers Vite and common UMI/Webpack layouts.
284
+ for (const script of Array.from(documentRef.querySelectorAll('script[src]'))) {
285
+ try {
286
+ const scriptUrl = new URL(script.src || script.getAttribute('src') || '', documentBaseUrl);
287
+ const assetDirectory = scriptUrl.pathname.match(/^(.*\/)(?:assets|static)\/[^/]+$/i);
288
+ if (assetDirectory) {
289
+ return new URL(assetDirectory[1], scriptUrl.origin).href;
290
+ }
291
+ if (/\/(?:umi|main|index)(?:[.-][^/]*)?\.(?:m?js)$/i.test(scriptUrl.pathname)) {
292
+ return new URL('./', scriptUrl).href;
293
+ }
294
+ }
295
+ catch {
296
+ // Ignore unrelated or malformed script URLs.
297
+ }
298
+ }
299
+ return documentBaseUrl;
300
+ };
198
301
  const buildOutlineItems = (items, prefix = 'outline', getFallbackTitle = index => `Outline ${index + 1}`) => items.map((item, index) => {
199
302
  const id = `${prefix}-${index}`;
200
303
  const children = Array.isArray(item.items)
@@ -209,7 +312,7 @@ const buildOutlineItems = (items, prefix = 'outline', getFallbackTitle = index =
209
312
  };
210
313
  });
211
314
  export default async function renderPdf(buffer, target, context) {
212
- var _a, _b, _c;
315
+ var _a, _b, _c, _d;
213
316
  const documentRef = target.ownerDocument || document;
214
317
  const targetWindow = documentRef.defaultView || (typeof window !== 'undefined' ? window : null);
215
318
  const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
@@ -218,6 +321,10 @@ export default async function renderPdf(buffer, target, context) {
218
321
  throw new Error(t('pdf.error.browserWindow'));
219
322
  }
220
323
  const options = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.pdf;
324
+ const pdfRuntimeAssetBaseUrl = resolvePdfRuntimeAssetBaseUrl(documentRef);
325
+ const cjkFontFallbackEnabled = (options === null || options === void 0 ? void 0 : options.cjkFontFallback) !== false;
326
+ const identityFontRepairEnabled = (options === null || options === void 0 ? void 0 : options.identityFontRepair) !== false;
327
+ const fontInspectionEnabled = cjkFontFallbackEnabled || identityFontRepairEnabled;
221
328
  const initialViewState = (options === null || options === void 0 ? void 0 : options.initialViewState) || ((_b = context === null || context === void 0 ? void 0 : context.options) === null || _b === void 0 ? void 0 : _b.initialViewState) || null;
222
329
  const navigationEnabled = (options === null || options === void 0 ? void 0 : options.navigation) !== false;
223
330
  const toolbarVisible = (options === null || options === void 0 ? void 0 : options.toolbar) !== false;
@@ -260,6 +367,10 @@ export default async function renderPdf(buffer, target, context) {
260
367
  let pdfSearchWaiters = [];
261
368
  const pdfThumbnails = new Map();
262
369
  const pendingPdfThumbnails = new Set();
370
+ const pdfCjkFontFallbackPageLoads = new Map();
371
+ const pdfCjkFontFallbackRenderHandledPages = new Set();
372
+ let pdfCjkFontFallbackManager = null;
373
+ let restorePdfJsMissingSystemFontWarnings = () => { };
263
374
  const pdfContext = {
264
375
  viewer: null,
265
376
  linkService: null,
@@ -269,6 +380,17 @@ export default async function renderPdf(buffer, target, context) {
269
380
  document: null,
270
381
  search: '',
271
382
  };
383
+ const ensurePdfPageCjkFontFallback = (pageNumber, page) => {
384
+ if (!pdfCjkFontFallbackManager) {
385
+ return Promise.resolve(false);
386
+ }
387
+ let pending = pdfCjkFontFallbackPageLoads.get(pageNumber);
388
+ if (!pending) {
389
+ pending = pdfCjkFontFallbackManager.ensurePage(page);
390
+ pdfCjkFontFallbackPageLoads.set(pageNumber, pending);
391
+ }
392
+ return pending;
393
+ };
272
394
  const root = createElement(documentRef, 'div', 'pdf-shell');
273
395
  root.dataset.viewerSearchProvider = 'pdf';
274
396
  root.dataset.viewerZoomProvider = 'pdf';
@@ -426,6 +548,7 @@ export default async function renderPdf(buffer, target, context) {
426
548
  if (destroyed || pdfContext.document !== pdfDocument) {
427
549
  return;
428
550
  }
551
+ await ensurePdfPageCjkFontFallback(pageNumber, page);
429
552
  const baseViewport = page.getViewport({
430
553
  scale: PixelsPerInch.PDF_TO_CSS_UNITS,
431
554
  rotation: currentRotation,
@@ -444,7 +567,7 @@ export default async function renderPdf(buffer, target, context) {
444
567
  }
445
568
  canvas.width = Math.max(1, Math.ceil(renderViewport.width));
446
569
  canvas.height = Math.max(1, Math.ceil(renderViewport.height));
447
- await page.render({ canvasContext, viewport: renderViewport }).promise;
570
+ await page.render({ canvas, canvasContext, viewport: renderViewport }).promise;
448
571
  if (destroyed || pdfContext.document !== pdfDocument) {
449
572
  return;
450
573
  }
@@ -463,6 +586,64 @@ export default async function renderPdf(buffer, target, context) {
463
586
  pendingPdfThumbnails.delete(pageNumber);
464
587
  }
465
588
  };
589
+ const renderFirstPageThumbnail = async (captureOptions) => {
590
+ var _a, _b, _c, _d, _e, _f;
591
+ const pdfDocument = pdfContext.document;
592
+ if (!pdfDocument) {
593
+ return null;
594
+ }
595
+ if ((_a = captureOptions.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
596
+ throw captureOptions.signal.reason;
597
+ }
598
+ const page = await pdfDocument.getPage(1);
599
+ await ensurePdfPageCjkFontFallback(1, page);
600
+ const baseViewport = page.getViewport({ scale: 1, rotation: currentRotation });
601
+ const scale = Math.max(0.1, Math.min(captureOptions.width / Math.max(baseViewport.width, 1), captureOptions.height / Math.max(baseViewport.height, 1)));
602
+ const viewport = page.getViewport({ scale, rotation: currentRotation });
603
+ const canvas = documentRef.createElement('canvas');
604
+ const canvasContext = canvas.getContext('2d');
605
+ if (!canvasContext) {
606
+ return null;
607
+ }
608
+ canvas.width = Math.max(1, Math.ceil(viewport.width));
609
+ canvas.height = Math.max(1, Math.ceil(viewport.height));
610
+ const renderTask = page.render({ canvas, canvasContext, viewport });
611
+ const cancelRender = () => renderTask.cancel();
612
+ (_b = captureOptions.signal) === null || _b === void 0 ? void 0 : _b.addEventListener('abort', cancelRender, { once: true });
613
+ try {
614
+ await renderTask.promise;
615
+ if ((_c = captureOptions.signal) === null || _c === void 0 ? void 0 : _c.aborted) {
616
+ throw captureOptions.signal.reason;
617
+ }
618
+ return await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
619
+ }
620
+ catch (error) {
621
+ if ((_d = captureOptions.signal) === null || _d === void 0 ? void 0 : _d.aborted) {
622
+ throw captureOptions.signal.reason;
623
+ }
624
+ throw error;
625
+ }
626
+ finally {
627
+ (_e = captureOptions.signal) === null || _e === void 0 ? void 0 : _e.removeEventListener('abort', cancelRender);
628
+ canvas.width = 0;
629
+ canvas.height = 0;
630
+ (_f = page.cleanup) === null || _f === void 0 ? void 0 : _f.call(page);
631
+ }
632
+ };
633
+ (_d = context === null || context === void 0 ? void 0 : context.registerThumbnailAdapter) === null || _d === void 0 ? void 0 : _d.call(context, {
634
+ beforeCapture: async ({ signal }) => {
635
+ while (loadStatus === 'loading' && !destroyed) {
636
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
637
+ throw signal.reason;
638
+ }
639
+ await new Promise(resolve => targetWindow.setTimeout(resolve, 16));
640
+ }
641
+ if (loadStatus === 'error') {
642
+ throw new Error(errorMessage || t('pdf.error.loadFailed'));
643
+ }
644
+ },
645
+ capture: renderFirstPageThumbnail,
646
+ });
466
647
  const ensureThumbnailObserver = () => {
467
648
  if (!thumbnailsEnabled || thumbnailObserver || typeof targetWindow.IntersectionObserver !== 'function') {
468
649
  return;
@@ -597,7 +778,7 @@ export default async function renderPdf(buffer, target, context) {
597
778
  }
598
779
  };
599
780
  const createPdfWorker = async () => {
600
- const workerUrl = resolvePdfWorkerUrl(options, documentRef.baseURI || documentRef.URL || undefined);
781
+ const workerUrl = resolvePdfWorkerUrl(options, pdfRuntimeAssetBaseUrl);
601
782
  const hasExplicitWorkerUrl = isConfiguredUrl(options === null || options === void 0 ? void 0 : options.workerUrl);
602
783
  const shouldUseRealWorker = !!(targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.Worker) &&
603
784
  (hasExplicitWorkerUrl || await canUseResolvedPdfWorkerUrl(workerUrl));
@@ -1195,6 +1376,7 @@ export default async function renderPdf(buffer, target, context) {
1195
1376
  throw new Error(t('pdf.error.unloaded'));
1196
1377
  }
1197
1378
  const page = await pdfDocument.getPage(pageNumber);
1379
+ await ensurePdfPageCjkFontFallback(pageNumber, page);
1198
1380
  const baseViewport = page.getViewport({
1199
1381
  scale: PixelsPerInch.PDF_TO_CSS_UNITS,
1200
1382
  rotation: currentRotation,
@@ -1213,7 +1395,7 @@ export default async function renderPdf(buffer, target, context) {
1213
1395
  }
1214
1396
  canvas.width = Math.ceil(renderViewport.width);
1215
1397
  canvas.height = Math.ceil(renderViewport.height);
1216
- await page.render({ canvasContext, viewport: renderViewport }).promise;
1398
+ await page.render({ canvas, canvasContext, viewport: renderViewport }).promise;
1217
1399
  const pageTitle = t('pdf.export.pageTitle', { title: exportOptions.title, page: pageNumber });
1218
1400
  const pageStyle = [
1219
1401
  `--viewer-print-page-width:${formatCssPixels(pageWidth)}`,
@@ -1229,8 +1411,13 @@ export default async function renderPdf(buffer, target, context) {
1229
1411
  return `<div class="pdf-export-document">${pagesHtml.join('')}</div>`;
1230
1412
  };
1231
1413
  const loadFile = async () => {
1232
- var _a, _b;
1414
+ var _a, _b, _c;
1233
1415
  const requestVersion = ++loadVersion;
1416
+ restorePdfJsMissingSystemFontWarnings();
1417
+ restorePdfJsMissingSystemFontWarnings = () => { };
1418
+ pdfCjkFontFallbackManager = null;
1419
+ pdfCjkFontFallbackPageLoads.clear();
1420
+ pdfCjkFontFallbackRenderHandledPages.clear();
1234
1421
  loadStatus = 'loading';
1235
1422
  errorMessage = '';
1236
1423
  pdfContext.document = null;
@@ -1258,6 +1445,7 @@ export default async function renderPdf(buffer, target, context) {
1258
1445
  linkService: pdfLinkService,
1259
1446
  findController: pdfFindController,
1260
1447
  l10n: new GenericL10n(resolvedLocale),
1448
+ enableAutoLinking: false,
1261
1449
  });
1262
1450
  pdfContext.viewer = pdfViewer;
1263
1451
  pdfContext.linkService = pdfLinkService;
@@ -1314,12 +1502,46 @@ export default async function renderPdf(buffer, target, context) {
1314
1502
  emitViewStateChange('zoom-change', 'viewer');
1315
1503
  }
1316
1504
  });
1317
- eventBus.on('pagerendered', scheduleLegacyPageDimensionPatch);
1505
+ eventBus.on('pagerendered', ({ pageNumber }) => {
1506
+ scheduleLegacyPageDimensionPatch();
1507
+ if (!pdfCjkFontFallbackManager ||
1508
+ pdfCjkFontFallbackRenderHandledPages.has(pageNumber)) {
1509
+ return;
1510
+ }
1511
+ pdfCjkFontFallbackRenderHandledPages.add(pageNumber);
1512
+ const pdfDocument = pdfContext.document;
1513
+ const alreadyPrepared = pdfCjkFontFallbackPageLoads.has(pageNumber);
1514
+ if (!pdfDocument || alreadyPrepared) {
1515
+ return;
1516
+ }
1517
+ void pdfDocument.getPage(pageNumber)
1518
+ .then(page => ensurePdfPageCjkFontFallback(pageNumber, page))
1519
+ .then(fontLoaded => {
1520
+ var _a;
1521
+ if (fontLoaded &&
1522
+ !destroyed &&
1523
+ pdfContext.document === pdfDocument) {
1524
+ (_a = pdfContext.viewer) === null || _a === void 0 ? void 0 : _a.refresh();
1525
+ }
1526
+ })
1527
+ .catch(error => {
1528
+ console.warn('[file-viewer] Unable to inspect a PDF page for CJK font fallback.', error);
1529
+ });
1530
+ });
1318
1531
  if (!(context === null || context === void 0 ? void 0 : context.streamUrl) && !buffer.byteLength) {
1319
1532
  throw new Error(t('pdf.error.missingSource'));
1320
1533
  }
1321
- const worker = await createPdfWorker();
1322
- const pdfAssets = resolveFileViewerPdfAssetUrls(options, documentRef.baseURI || documentRef.URL);
1534
+ const pdfAssets = resolveFileViewerPdfAssetUrls(options, pdfRuntimeAssetBaseUrl);
1535
+ if (cjkFontFallbackEnabled) {
1536
+ pdfCjkFontFallbackManager = createPdfCjkFontFallbackManager({
1537
+ documentRef,
1538
+ fontAssetPath: pdfAssets.cjkFontFallbackPath,
1539
+ onWarning: (message, error) => {
1540
+ console.warn(`[file-viewer] ${message}`, error || '');
1541
+ },
1542
+ });
1543
+ restorePdfJsMissingSystemFontWarnings = suppressPdfJsMissingSystemFontWarnings(targetWindow);
1544
+ }
1323
1545
  const source = (context === null || context === void 0 ? void 0 : context.streamUrl)
1324
1546
  ? {
1325
1547
  url: context.streamUrl,
@@ -1329,18 +1551,24 @@ export default async function renderPdf(buffer, target, context) {
1329
1551
  : {
1330
1552
  data: buffer,
1331
1553
  };
1332
- const loadingTask = getDocument({
1333
- ...source,
1334
- worker: worker || undefined,
1335
- cMapUrl: pdfAssets.cMapUrl,
1336
- standardFontDataUrl: pdfAssets.standardFontDataUrl,
1337
- useWorkerFetch: true,
1338
- cMapPacked: true,
1339
- enableXfa: true,
1340
- });
1341
- resource = { loadingTask, worker };
1554
+ const createLoadingResource = async (loadingSource) => {
1555
+ const worker = await createPdfWorker();
1556
+ const loadingTask = getDocument({
1557
+ ...loadingSource,
1558
+ worker: worker || undefined,
1559
+ cMapUrl: pdfAssets.cMapUrl,
1560
+ wasmUrl: pdfAssets.wasmUrl,
1561
+ standardFontDataUrl: pdfAssets.standardFontDataUrl,
1562
+ useWorkerFetch: true,
1563
+ cMapPacked: true,
1564
+ enableXfa: true,
1565
+ fontExtraProperties: fontInspectionEnabled,
1566
+ });
1567
+ return { loadingTask, worker };
1568
+ };
1569
+ resource = await createLoadingResource(source);
1342
1570
  pdfContext.resource = resource;
1343
- const pdfDocument = await loadingTask.promise;
1571
+ let pdfDocument = await resource.loadingTask.promise;
1344
1572
  if (destroyed || requestVersion !== loadVersion || pdfContext.resource !== resource) {
1345
1573
  if (pdfContext.resource === resource) {
1346
1574
  pdfContext.resource = null;
@@ -1348,10 +1576,86 @@ export default async function renderPdf(buffer, target, context) {
1348
1576
  }
1349
1577
  return;
1350
1578
  }
1579
+ let firstPageTextContent = null;
1580
+ let firstPageForInspection = null;
1581
+ if (fontInspectionEnabled && pdfDocument.numPages > 0) {
1582
+ const firstPage = await pdfDocument.getPage(1);
1583
+ firstPageForInspection = firstPage;
1584
+ firstPageTextContent = await firstPageForInspection.getTextContent();
1585
+ }
1586
+ if (identityFontRepairEnabled && firstPageTextContent) {
1587
+ const malformedFontNames = collectMalformedIdentityFontNames(firstPageTextContent);
1588
+ if (malformedFontNames.length && firstPageForInspection) {
1589
+ await firstPageForInspection.getOperatorList();
1590
+ }
1591
+ const malformedFontFamilies = new Map();
1592
+ for (const fontName of malformedFontNames) {
1593
+ try {
1594
+ const family = (_b = firstPageForInspection === null || firstPageForInspection === void 0 ? void 0 : firstPageForInspection.commonObjs.get(fontName)) === null || _b === void 0 ? void 0 : _b.name;
1595
+ if (family) {
1596
+ malformedFontFamilies.set(fontName, family);
1597
+ }
1598
+ }
1599
+ catch {
1600
+ // A font object that is still unresolved cannot be repaired safely.
1601
+ }
1602
+ }
1603
+ const candidateFamilies = detectMalformedIdentityCjkFontFamilies(firstPageTextContent, fontName => malformedFontFamilies.get(fontName) || '');
1604
+ if (candidateFamilies.length) {
1605
+ let replacementResource = null;
1606
+ try {
1607
+ const sourceBytes = await pdfDocument.getData();
1608
+ const { repairMalformedIdentityCjkFonts } = await import('./pdfIdentityFontRepair.js');
1609
+ const repaired = await repairMalformedIdentityCjkFonts(sourceBytes, candidateFamilies);
1610
+ if (repaired.repairedFonts > 0) {
1611
+ const previousResource = resource;
1612
+ replacementResource = await createLoadingResource({ data: repaired.bytes });
1613
+ const replacementDocument = await replacementResource.loadingTask.promise;
1614
+ const repairedFirstPage = await replacementDocument.getPage(1);
1615
+ const replacementTextContent = await repairedFirstPage.getTextContent();
1616
+ if (destroyed ||
1617
+ requestVersion !== loadVersion ||
1618
+ pdfContext.resource !== previousResource) {
1619
+ await destroyPdfResource(replacementResource);
1620
+ return;
1621
+ }
1622
+ resource = replacementResource;
1623
+ replacementResource = null;
1624
+ pdfContext.resource = resource;
1625
+ pdfDocument = replacementDocument;
1626
+ firstPageTextContent = replacementTextContent;
1627
+ await destroyPdfResource(previousResource);
1628
+ console.info(`[file-viewer] Repaired ${repaired.repairedFonts} malformed PDF Identity CJK font mapping(s).`);
1629
+ }
1630
+ }
1631
+ catch (error) {
1632
+ if (replacementResource) {
1633
+ await destroyPdfResource(replacementResource);
1634
+ }
1635
+ console.warn('[file-viewer] Unable to repair a malformed PDF Identity CJK font; continuing with the original preview.', error);
1636
+ }
1637
+ }
1638
+ }
1639
+ if (destroyed || requestVersion !== loadVersion || pdfContext.resource !== resource) {
1640
+ return;
1641
+ }
1351
1642
  pageCount = pdfDocument.numPages;
1352
1643
  currentPage = 1;
1353
1644
  pdfContext.document = pdfDocument;
1354
- (_b = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _b === void 0 ? void 0 : _b.call(context, {
1645
+ if (pdfCjkFontFallbackManager && pageCount > 0) {
1646
+ if (firstPageTextContent) {
1647
+ pdfCjkFontFallbackPageLoads.set(1, pdfCjkFontFallbackManager.ensureTextContent(firstPageTextContent));
1648
+ await pdfCjkFontFallbackPageLoads.get(1);
1649
+ }
1650
+ else {
1651
+ const firstPage = await pdfDocument.getPage(1);
1652
+ await ensurePdfPageCjkFontFallback(1, firstPage);
1653
+ }
1654
+ if (destroyed || requestVersion !== loadVersion || pdfContext.document !== pdfDocument) {
1655
+ return;
1656
+ }
1657
+ }
1658
+ (_c = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _c === void 0 ? void 0 : _c.call(context, {
1355
1659
  includeDocumentStyles: false,
1356
1660
  printStyle: buildPdfPrintStyle,
1357
1661
  toHtml: renderPdfPagesForExport,
@@ -1433,9 +1737,14 @@ export default async function renderPdf(buffer, target, context) {
1433
1737
  return {
1434
1738
  $el: root,
1435
1739
  unmount() {
1436
- var _a;
1740
+ var _a, _b;
1437
1741
  destroyed = true;
1438
1742
  loadVersion += 1;
1743
+ restorePdfJsMissingSystemFontWarnings();
1744
+ restorePdfJsMissingSystemFontWarnings = () => { };
1745
+ pdfCjkFontFallbackManager = null;
1746
+ pdfCjkFontFallbackPageLoads.clear();
1747
+ pdfCjkFontFallbackRenderHandledPages.clear();
1439
1748
  targetWindow.cancelAnimationFrame(fitFrame);
1440
1749
  targetWindow.cancelAnimationFrame(pageDimensionFrame);
1441
1750
  targetWindow.cancelAnimationFrame(scrollStateFrame);
@@ -1449,6 +1758,7 @@ export default async function renderPdf(buffer, target, context) {
1449
1758
  unregisterFileViewerViewStateProvider(root);
1450
1759
  outlineItems = [];
1451
1760
  (_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
1761
+ (_b = context === null || context === void 0 ? void 0 : context.registerThumbnailAdapter) === null || _b === void 0 ? void 0 : _b.call(context, null);
1452
1762
  const resource = pdfContext.resource;
1453
1763
  pdfContext.viewer = null;
1454
1764
  pdfContext.linkService = null;
@@ -0,0 +1,33 @@
1
+ type PdfTextContentItem = {
2
+ str?: string;
3
+ fontName?: string;
4
+ };
5
+ type PdfTextContentStyle = {
6
+ fontSubstitution?: string;
7
+ };
8
+ export type PdfTextContent = {
9
+ items?: PdfTextContentItem[];
10
+ styles?: Record<string, PdfTextContentStyle>;
11
+ };
12
+ export type PdfTextContentPage = {
13
+ getTextContent: () => Promise<PdfTextContent>;
14
+ };
15
+ export interface PdfCjkFontFallbackManager {
16
+ prepare: () => Promise<boolean>;
17
+ ensureTextContent: (textContent: PdfTextContent) => Promise<boolean>;
18
+ ensurePage: (page: PdfTextContentPage) => Promise<boolean>;
19
+ }
20
+ export interface CreatePdfCjkFontFallbackManagerOptions {
21
+ documentRef: Document;
22
+ fontAssetPath: string;
23
+ onWarning?: (message: string, error?: unknown) => void;
24
+ }
25
+ /**
26
+ * Some PDF generators write TrueType glyph IDs through Identity-H but omit
27
+ * ToUnicode. PDF.js then exposes those glyph IDs as control characters, so a
28
+ * replacement font alone cannot recover the intended text.
29
+ */
30
+ export declare const detectMalformedIdentityCjkFontFamilies: (textContent: PdfTextContent, resolveFontFamily?: (fontName: string) => string) => string[];
31
+ export declare const collectMalformedIdentityFontNames: (textContent: PdfTextContent) => string[];
32
+ export declare const createPdfCjkFontFallbackManager: ({ documentRef, fontAssetPath, onWarning, }: CreatePdfCjkFontFallbackManagerOptions) => PdfCjkFontFallbackManager;
33
+ export {};