@open-file-viewer/core 0.1.22 → 0.1.23

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.md CHANGED
@@ -123,6 +123,14 @@ pdfPlugin({
123
123
  This keeps compatibility at the cost of holding one extra copy of the PDF in memory, so use it only
124
124
  for affected environments.
125
125
 
126
+ For 360 Secure Browser, 360 Extreme Browser, and older Chromium kernels, `pdfPlugin()` defaults to
127
+ `compatibilityMode: "auto"`. It installs the `Promise.withResolvers` compatibility shim when needed
128
+ and selects the matching PDF.js legacy worker. If an enterprise browser hides its 360 user-agent
129
+ marker, force the compatibility path with `pdfPlugin({ compatibilityMode: "legacy" })`. When
130
+ self-hosting that worker, point `workerSrc` to the same-version
131
+ `pdfjs-dist/legacy/build/pdf.worker.min.mjs`. Use `compatibilityMode: "modern"` only when targeting
132
+ modern Chrome or Edge exclusively.
133
+
126
134
  ## High-Fidelity Office Conversion
127
135
 
128
136
  Browser-side Office renderers cannot perfectly reproduce Word/WPS layout for files with anchored
@@ -251,7 +259,7 @@ Use `toolbar.render(ctx)` when you need to replace the toolbar completely. The c
251
259
 
252
260
  ## Locale and Fallback Text
253
261
 
254
- Fallback text defaults to English. Set `locale: "zh-CN"` for Simplified Chinese built-in loading and unsupported-file messages, or override individual strings with `messages`:
262
+ Built-in states, fallback panels, toolbar labels, and plugin messages default to English. Set `locale: "zh-CN"` for Simplified Chinese, or override individual PDF, image, text, Office, and custom-plugin strings with `messages`:
255
263
 
256
264
  ```ts
257
265
  createViewer({
@@ -260,12 +268,16 @@ createViewer({
260
268
  locale: "zh-CN",
261
269
  messages: {
262
270
  unsupportedTitle: "No inline preview available",
263
- downloadFile: "Download original file"
271
+ downloadFile: "Download original file",
272
+ pdfPreviewFailedTitle: "Unable to open report",
273
+ imageDownload: "Save original image"
264
274
  },
265
275
  plugins
266
276
  });
267
277
  ```
268
278
 
279
+ The PDF viewer includes a synchronized page navigator with previous/next buttons and a numeric page input. Enter a page number and press Enter to jump directly to it.
280
+
269
281
  ## License
270
282
 
271
283
  MIT
package/dist/index.cjs CHANGED
@@ -655,7 +655,36 @@ var defaultMessages = {
655
655
  officeUnsupportedIntro: "\u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002{message}",
656
656
  officeUnsupportedSupportedFormats: "\u5F53\u524D\u7248\u672C\u4F18\u5148\u652F\u6301 docx\u3001rtf\u3001odt/fodt\u3001xlsx/xls/csv/ods\u3001pptx/ppsx\u3001odp/fodp \u7684\u57FA\u7840\u5185\u5BB9\u9884\u89C8\u3002",
657
657
  officeErrorWithMessage: "\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A{message}",
658
- officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002"
658
+ officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002",
659
+ officeConvertedTitle: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
660
+ officeConvertedPdfFailed: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
661
+ pdfEncryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
662
+ pdfEncryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
663
+ pdfPreviewFailedTitle: "PDF \u9884\u89C8\u5931\u8D25",
664
+ pdfCorruptedMessage: "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002",
665
+ pdfCannotLoadMessage: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u52A0\u8F7D\u8BE5 PDF\u3002",
666
+ pdfDownload: "\u4E0B\u8F7D PDF",
667
+ pdfPageLoading: "\u9875\u9762 {page} \u52A0\u8F7D\u4E2D...",
668
+ pdfPageEmpty: "\u8BE5\u9875\u6CA1\u6709\u68C0\u6D4B\u5230\u53EF\u663E\u793A\u7684 PDF \u517C\u5BB9\u5185\u5BB9\u3002\u82E5\u8FD9\u662F Illustrator/AI \u6587\u4EF6\uFF0C\u53EF\u80FD\u53EA\u5305\u542B\u79C1\u6709\u7F16\u8F91\u6570\u636E\uFF0C\u5EFA\u8BAE\u5BFC\u51FA\u4E3A PDF/SVG/PNG \u540E\u9884\u89C8\u3002",
669
+ pdfPageRenderFailed: "\u65E0\u6CD5\u6E32\u67D3\u8BE5\u9875\u9762\u3002\u8BE5\u9875\u53EF\u80FD\u5305\u542B\u6D4F\u89C8\u5668 PDF \u5F15\u64CE\u6682\u4E0D\u652F\u6301\u7684\u56FE\u5F62\u3001\u5B57\u4F53\u6216\u538B\u7F29\u7279\u6027\u3002",
670
+ pdfPreviousPage: "\u4E0A\u4E00\u9875",
671
+ pdfNextPage: "\u4E0B\u4E00\u9875",
672
+ pdfPageInput: "\u5F53\u524D\u9875\u7801",
673
+ pdfPagePosition: "/ {total}",
674
+ pdfPageLabel: "\u7B2C {page} \u9875",
675
+ pdfSummaryPages: "\u9875\u6570",
676
+ pdfSummaryPageSizes: "\u9875\u9762\u5C3A\u5BF8",
677
+ pdfSummaryFit: "\u9002\u914D",
678
+ pdfSummaryActualSize: "\u539F\u59CB\u5927\u5C0F",
679
+ pdfSummaryFitWidth: "\u9002\u5408\u5BBD\u5EA6",
680
+ pdfSummaryZoom: "\u7F29\u653E",
681
+ imagePreviewFailedTitle: "\u56FE\u7247\u9884\u89C8\u5931\u8D25",
682
+ imagePreviewFailedMessage: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u76F4\u63A5\u663E\u793A\u8BE5\u56FE\u7247\uFF0C\u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u7F16\u7801\u6682\u4E0D\u53D7\u652F\u6301\u3002",
683
+ imageDownload: "\u4E0B\u8F7D\u56FE\u7247",
684
+ imageZoomOut: "\u7F29\u5C0F",
685
+ imageZoomIn: "\u653E\u5927",
686
+ imageRotate: "\u65CB\u8F6C\u56FE\u7247",
687
+ imageReset: "\u91CD\u7F6E\u56FE\u7247\u89C6\u56FE"
659
688
  },
660
689
  "en-US": {
661
690
  loading: "Loading preview...",
@@ -704,7 +733,36 @@ var defaultMessages = {
704
733
  officeUnsupportedIntro: "is handled by the Office plugin. {message}",
705
734
  officeUnsupportedSupportedFormats: "This version prioritizes basic previews for docx, rtf, odt/fodt, xlsx/xls/csv/ods, pptx/ppsx, and odp/fodp files.",
706
735
  officeErrorWithMessage: "Parser returned: {message}",
707
- officeErrorWithoutMessage: "Parser did not return a specific error."
736
+ officeErrorWithoutMessage: "Parser did not return a specific error.",
737
+ officeConvertedTitle: "High-fidelity Office conversion preview",
738
+ officeConvertedPdfFailed: "The converted Office PDF could not be previewed",
739
+ pdfEncryptedTitle: "This PDF is encrypted and cannot be previewed online",
740
+ pdfEncryptedMessage: "Download and open it with the password, or upload a decrypted PDF file.",
741
+ pdfPreviewFailedTitle: "PDF preview failed",
742
+ pdfCorruptedMessage: "The PDF may be corrupted or use an invalid format.",
743
+ pdfCannotLoadMessage: "This browser could not load the PDF.",
744
+ pdfDownload: "Download PDF",
745
+ pdfPageLoading: "Loading page {page}...",
746
+ pdfPageEmpty: "No displayable PDF-compatible content was detected on this page. If this is an Illustrator/AI file, export it as PDF, SVG, or PNG before previewing.",
747
+ pdfPageRenderFailed: "This page could not be rendered. It may contain graphics, fonts, or compression features that the browser PDF engine does not support.",
748
+ pdfPreviousPage: "Previous page",
749
+ pdfNextPage: "Next page",
750
+ pdfPageInput: "Current page",
751
+ pdfPagePosition: "/ {total}",
752
+ pdfPageLabel: "Page {page}",
753
+ pdfSummaryPages: "Pages",
754
+ pdfSummaryPageSizes: "Page sizes",
755
+ pdfSummaryFit: "Fit",
756
+ pdfSummaryActualSize: "Actual size",
757
+ pdfSummaryFitWidth: "Fit width",
758
+ pdfSummaryZoom: "Zoom",
759
+ imagePreviewFailedTitle: "Image preview failed",
760
+ imagePreviewFailedMessage: "This browser cannot display the image. The file may be corrupted or use an unsupported encoding.",
761
+ imageDownload: "Download image",
762
+ imageZoomOut: "Zoom out",
763
+ imageZoomIn: "Zoom in",
764
+ imageRotate: "Rotate image",
765
+ imageReset: "Reset image view"
708
766
  }
709
767
  };
710
768
  function resolveMessages(options) {
@@ -2215,7 +2273,7 @@ function imagePlugin() {
2215
2273
  infoBar.hidden = false;
2216
2274
  infoBar.removeAttribute("aria-hidden");
2217
2275
  infoBar.style.removeProperty("display");
2218
- stage.replaceChildren(createImageFallback(ctx.file.name, url));
2276
+ stage.replaceChildren(createImageFallback(ctx.file.name, url, ctx.options.messages));
2219
2277
  ctx.toolbar?.refreshCommandSupport();
2220
2278
  };
2221
2279
  const setScale = (nextScale) => {
@@ -2240,13 +2298,13 @@ function imagePlugin() {
2240
2298
  return () => button.removeEventListener("click", action);
2241
2299
  };
2242
2300
  const disposers = showInlineControls ? [
2243
- addButton("-", "Zoom out", () => setScale(scale - 0.25)),
2244
- addButton("+", "Zoom in", () => setScale(scale + 0.25)),
2245
- addButton("Rotate", "Rotate image", () => {
2301
+ addButton("-", ctx.options.messages.imageZoomOut, () => setScale(scale - 0.25)),
2302
+ addButton("+", ctx.options.messages.imageZoomIn, () => setScale(scale + 0.25)),
2303
+ addButton(ctx.options.messages.imageRotate, ctx.options.messages.imageRotate, () => {
2246
2304
  rotation += 90;
2247
2305
  updateTransform();
2248
2306
  }),
2249
- addButton("Reset", "Reset image view", reset)
2307
+ addButton(ctx.options.messages.imageReset, ctx.options.messages.imageReset, reset)
2250
2308
  ] : [];
2251
2309
  if (showInlineControls) {
2252
2310
  controls.append(zoomLabel);
@@ -2461,17 +2519,17 @@ function getTiffDimensions(ifd) {
2461
2519
  height: Number(ifd.height || ifd.t257 || 0)
2462
2520
  };
2463
2521
  }
2464
- function createImageFallback(fileName, url) {
2522
+ function createImageFallback(fileName, url, messages) {
2465
2523
  const fallback = document.createElement("div");
2466
2524
  fallback.className = "ofv-fallback";
2467
2525
  const title = document.createElement("strong");
2468
- title.textContent = "\u56FE\u7247\u9884\u89C8\u5931\u8D25";
2526
+ title.textContent = messages.imagePreviewFailedTitle;
2469
2527
  const meta = document.createElement("span");
2470
- meta.textContent = "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u76F4\u63A5\u663E\u793A\u8BE5\u56FE\u7247\uFF0C\u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u7F16\u7801\u6682\u4E0D\u53D7\u652F\u6301\u3002";
2528
+ meta.textContent = messages.imagePreviewFailedMessage;
2471
2529
  const download = document.createElement("a");
2472
2530
  download.href = url;
2473
2531
  download.download = fileName;
2474
- download.textContent = "\u4E0B\u8F7D\u56FE\u7247";
2532
+ download.textContent = messages.imageDownload;
2475
2533
  fallback.append(title, meta, download);
2476
2534
  return fallback;
2477
2535
  }
@@ -4832,17 +4890,20 @@ function pdfPlugin(options = {}) {
4832
4890
  fit: ctx.options.fit,
4833
4891
  zoom: ctx.options.zoom,
4834
4892
  toolbar: ctx.toolbar,
4835
- encryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4836
- encryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4837
- encryptedAction: "\u4E0B\u8F7D PDF",
4893
+ messages: ctx.options.messages,
4838
4894
  revokeUrlOnDestroy: true
4839
4895
  });
4840
4896
  }
4841
4897
  };
4842
4898
  }
4843
4899
  async function renderPdfDocumentPreview(options) {
4900
+ const useLegacyCompatibility = shouldUseLegacyPdfCompatibility(options.compatibilityMode);
4901
+ if (useLegacyCompatibility) {
4902
+ installPromiseWithResolversPolyfill();
4903
+ }
4844
4904
  const pdf = options.pdfjs || await import("pdfjs-dist");
4845
- configurePdfWorker(pdf, options.workerSrc);
4905
+ const messages = { ...defaultMessages["en-US"], ...options.messages };
4906
+ configurePdfWorker(pdf, options.workerSrc, useLegacyCompatibility);
4846
4907
  const viewer = document.createElement("div");
4847
4908
  viewer.className = "ofv-pdf-viewer";
4848
4909
  if (options.title) {
@@ -4871,10 +4932,10 @@ async function renderPdfDocumentPreview(options) {
4871
4932
  url: options.fileUrl
4872
4933
  };
4873
4934
  const fallback = isEncryptedError(error) ? createEncryptedFallback(fileLike, options.fileUrl, {
4874
- title: options.encryptedTitle || "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4875
- message: options.encryptedMessage || "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4876
- action: options.encryptedAction || "\u4E0B\u8F7D PDF"
4877
- }) : createPdfFallback(options.fileName, options.fileUrl, normalizePdfError(error), options.fallbackTitle);
4935
+ title: options.encryptedTitle || messages.pdfEncryptedTitle,
4936
+ message: options.encryptedMessage || messages.pdfEncryptedMessage,
4937
+ action: options.encryptedAction || messages.pdfDownload
4938
+ }) : createPdfFallback(options.fileName, options.fileUrl, normalizePdfError(error, messages), messages, options.fallbackTitle);
4878
4939
  if (!fallback.classList.contains("ofv-pdf-web-fallback")) {
4879
4940
  options.viewport.classList.add("ofv-center");
4880
4941
  }
@@ -4941,8 +5002,43 @@ async function renderPdfDocumentPreview(options) {
4941
5002
  let currentSize = options.size;
4942
5003
  let zoomFactor = getInitialZoom({ options: { zoom: options.zoom ?? 1 } }, 0.25, 4);
4943
5004
  let rotation = 0;
5005
+ let currentPage = 1;
5006
+ const goToPage = (page, scroll = true) => {
5007
+ currentPage = Math.min(pdfDocument.numPages, Math.max(1, Math.round(page) || 1));
5008
+ pageNavigator.setCurrent(currentPage);
5009
+ const wrapper = pageStates[currentPage - 1]?.wrapper;
5010
+ if (!scroll || !wrapper) {
5011
+ return;
5012
+ }
5013
+ const top = Math.max(0, wrapper.offsetTop - 16);
5014
+ if (typeof scroller.scrollTo === "function") {
5015
+ scroller.scrollTo({ top, behavior: "smooth" });
5016
+ } else {
5017
+ scroller.scrollTop = top;
5018
+ }
5019
+ };
5020
+ const pageNavigator = createPdfPageNavigator(pdfDocument.numPages, messages, (page) => goToPage(page));
5021
+ viewer.insertBefore(pageNavigator.element, summary);
5022
+ const handleScrollerScroll = () => {
5023
+ const scrollerRect = scroller.getBoundingClientRect();
5024
+ const targetY = scrollerRect.top + Math.min(Math.max(scroller.clientHeight * 0.25, 40), 180);
5025
+ let nearestPage = currentPage;
5026
+ let nearestDistance = Number.POSITIVE_INFINITY;
5027
+ pageStates.forEach((state, index) => {
5028
+ const distance = Math.abs(state.wrapper.getBoundingClientRect().top - targetY);
5029
+ if (distance < nearestDistance) {
5030
+ nearestDistance = distance;
5031
+ nearestPage = index + 1;
5032
+ }
5033
+ });
5034
+ if (nearestPage !== currentPage) {
5035
+ currentPage = nearestPage;
5036
+ pageNavigator.setCurrent(currentPage);
5037
+ }
5038
+ };
5039
+ scroller.addEventListener("scroll", handleScrollerScroll, { passive: true });
4944
5040
  const updateSummary = () => {
4945
- renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor);
5041
+ renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor, messages);
4946
5042
  options.toolbar?.setZoom(zoomFactor);
4947
5043
  };
4948
5044
  const clearPage = (pageIdx) => {
@@ -4958,7 +5054,9 @@ async function renderPdfDocumentPreview(options) {
4958
5054
  state.canvas = null;
4959
5055
  state.rendered = false;
4960
5056
  state.wrapper.replaceChildren();
4961
- state.wrapper.append(createPageStatus("ofv-pdf-skeleton", `\u9875\u9762 ${pageIdx + 1} \u52A0\u8F7D\u4E2D...`));
5057
+ state.wrapper.append(
5058
+ createPageStatus("ofv-pdf-skeleton", formatPreviewMessage(messages.pdfPageLoading, { page: pageIdx + 1 }))
5059
+ );
4962
5060
  };
4963
5061
  const renderPage = async (pageIdx, size) => {
4964
5062
  const state = pageStates[pageIdx];
@@ -5026,7 +5124,7 @@ async function renderPdfDocumentPreview(options) {
5026
5124
  state.wrapper.appendChild(
5027
5125
  createPageStatus(
5028
5126
  "ofv-pdf-empty",
5029
- "\u8BE5\u9875\u6CA1\u6709\u68C0\u6D4B\u5230\u53EF\u663E\u793A\u7684 PDF \u517C\u5BB9\u5185\u5BB9\u3002\u82E5\u8FD9\u662F Illustrator/AI \u6587\u4EF6\uFF0C\u53EF\u80FD\u53EA\u5305\u542B\u79C1\u6709\u7F16\u8F91\u6570\u636E\uFF0C\u5EFA\u8BAE\u5BFC\u51FA\u4E3A PDF/SVG/PNG \u540E\u9884\u89C8\u3002"
5127
+ messages.pdfPageEmpty
5030
5128
  )
5031
5129
  );
5032
5130
  }
@@ -5034,7 +5132,7 @@ async function renderPdfDocumentPreview(options) {
5034
5132
  console.error(`Failed to render PDF page ${pageIdx + 1}:`, err);
5035
5133
  state.rendered = false;
5036
5134
  state.wrapper.replaceChildren(
5037
- createPageStatus("ofv-pdf-error", "\u65E0\u6CD5\u6E32\u67D3\u8BE5\u9875\u9762\u3002\u8BE5\u9875\u53EF\u80FD\u5305\u542B\u6D4F\u89C8\u5668 PDF \u5F15\u64CE\u6682\u4E0D\u652F\u6301\u7684\u56FE\u5F62\u3001\u5B57\u4F53\u6216\u538B\u7F29\u7279\u6027\u3002")
5135
+ createPageStatus("ofv-pdf-error", messages.pdfPageRenderFailed)
5038
5136
  );
5039
5137
  }
5040
5138
  };
@@ -5075,9 +5173,10 @@ async function renderPdfDocumentPreview(options) {
5075
5173
  const wrapper = document.createElement("div");
5076
5174
  wrapper.className = "ofv-pdf-page-wrapper";
5077
5175
  wrapper.setAttribute("data-page-index", String(i));
5176
+ wrapper.setAttribute("aria-label", formatPreviewMessage(messages.pdfPageLabel, { page: i + 1 }));
5078
5177
  wrapper.style.width = `${w}px`;
5079
5178
  wrapper.style.height = `${h}px`;
5080
- wrapper.append(createPageStatus("ofv-pdf-skeleton", `\u9875\u9762 ${i + 1} \u52A0\u8F7D\u4E2D...`));
5179
+ wrapper.append(createPageStatus("ofv-pdf-skeleton", formatPreviewMessage(messages.pdfPageLoading, { page: i + 1 })));
5081
5180
  scroller.appendChild(wrapper);
5082
5181
  pageStates.push({
5083
5182
  wrapper,
@@ -5099,6 +5198,7 @@ async function renderPdfDocumentPreview(options) {
5099
5198
  }
5100
5199
  }, 0);
5101
5200
  }
5201
+ goToPage(currentPage, false);
5102
5202
  };
5103
5203
  renderLayout(options.size);
5104
5204
  let resizeTimer;
@@ -5139,6 +5239,8 @@ async function renderPdfDocumentPreview(options) {
5139
5239
  },
5140
5240
  destroy() {
5141
5241
  options.toolbar?.setZoom(void 0);
5242
+ pageNavigator.destroy();
5243
+ scroller.removeEventListener("scroll", handleScrollerScroll);
5142
5244
  window.clearTimeout(resizeTimer);
5143
5245
  observer?.disconnect();
5144
5246
  pageStates.forEach((state) => {
@@ -5184,15 +5286,15 @@ function getPdfAvailableWidth(width) {
5184
5286
  const gutter = width < 160 ? 16 : 32;
5185
5287
  return Math.max(1, width - gutter);
5186
5288
  }
5187
- function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor) {
5289
+ function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor, messages) {
5188
5290
  summary.replaceChildren();
5189
- appendPdfSummary(summary, "\u9875\u6570", String(pages));
5291
+ appendPdfSummary(summary, messages.pdfSummaryPages, String(pages));
5190
5292
  const pageSizes = formatPdfPageSizes(pagesMeta);
5191
5293
  if (pageSizes) {
5192
- appendPdfSummary(summary, "\u9875\u9762\u5C3A\u5BF8", pageSizes);
5294
+ appendPdfSummary(summary, messages.pdfSummaryPageSizes, pageSizes);
5193
5295
  }
5194
- appendPdfSummary(summary, "\u9002\u914D", fit === "actual" ? "\u539F\u59CB\u5927\u5C0F" : "\u9002\u5408\u5BBD\u5EA6");
5195
- appendPdfSummary(summary, "\u7F29\u653E", `${Math.round(zoomFactor * 100)}%`);
5296
+ appendPdfSummary(summary, messages.pdfSummaryFit, fit === "actual" ? messages.pdfSummaryActualSize : messages.pdfSummaryFitWidth);
5297
+ appendPdfSummary(summary, messages.pdfSummaryZoom, `${Math.round(zoomFactor * 100)}%`);
5196
5298
  }
5197
5299
  function appendPdfSummary(parent, label, value) {
5198
5300
  const item = document.createElement("span");
@@ -5282,7 +5384,7 @@ function createPageStatus(className, text) {
5282
5384
  status.textContent = text;
5283
5385
  return status;
5284
5386
  }
5285
- function createPdfFallback(fileName, url, message, titleText = "PDF \u9884\u89C8\u5931\u8D25") {
5387
+ function createPdfFallback(fileName, url, message, messages, titleText = messages.pdfPreviewFailedTitle) {
5286
5388
  if (isEmbeddableRemoteUrl(url)) {
5287
5389
  return createPdfWebFallback(fileName, url);
5288
5390
  }
@@ -5295,7 +5397,7 @@ function createPdfFallback(fileName, url, message, titleText = "PDF \u9884\u89C8
5295
5397
  const download = document.createElement("a");
5296
5398
  download.href = url;
5297
5399
  download.download = fileName;
5298
- download.textContent = "\u4E0B\u8F7D PDF";
5400
+ download.textContent = messages.pdfDownload;
5299
5401
  fallback.append(title, meta, download);
5300
5402
  return fallback;
5301
5403
  }
@@ -5314,23 +5416,109 @@ function createPdfWebFallback(fileName, url) {
5314
5416
  function isEmbeddableRemoteUrl(url) {
5315
5417
  return /^https?:\/\//i.test(url);
5316
5418
  }
5317
- function normalizePdfError(error) {
5419
+ function normalizePdfError(error, messages) {
5318
5420
  const message = error instanceof Error ? error.message : String(error || "");
5319
5421
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
5320
5422
  const lower = `${name} ${message}`.toLowerCase();
5321
5423
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
5322
- return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
5424
+ return messages.pdfCorruptedMessage;
5323
5425
  }
5324
- return "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u52A0\u8F7D\u8BE5 PDF\u3002";
5426
+ return messages.pdfCannotLoadMessage;
5427
+ }
5428
+ function createPdfPageNavigator(total, messages, onChange) {
5429
+ const element = document.createElement("div");
5430
+ element.className = "ofv-pdf-page-navigator";
5431
+ const previous = document.createElement("button");
5432
+ previous.type = "button";
5433
+ previous.textContent = "\u2039";
5434
+ previous.title = messages.pdfPreviousPage;
5435
+ previous.setAttribute("aria-label", messages.pdfPreviousPage);
5436
+ const input = document.createElement("input");
5437
+ input.type = "number";
5438
+ input.min = "1";
5439
+ input.max = String(total);
5440
+ input.value = "1";
5441
+ input.inputMode = "numeric";
5442
+ input.setAttribute("aria-label", messages.pdfPageInput);
5443
+ const position = document.createElement("span");
5444
+ position.textContent = formatPreviewMessage(messages.pdfPagePosition, { total });
5445
+ const next = document.createElement("button");
5446
+ next.type = "button";
5447
+ next.textContent = "\u203A";
5448
+ next.title = messages.pdfNextPage;
5449
+ next.setAttribute("aria-label", messages.pdfNextPage);
5450
+ const commit = () => onChange(Number(input.value));
5451
+ const handleKeydown = (event) => {
5452
+ if (event.key === "Enter") {
5453
+ commit();
5454
+ }
5455
+ };
5456
+ const handlePrevious = () => onChange(Number(input.value) - 1);
5457
+ const handleNext = () => onChange(Number(input.value) + 1);
5458
+ input.addEventListener("change", commit);
5459
+ input.addEventListener("keydown", handleKeydown);
5460
+ previous.addEventListener("click", handlePrevious);
5461
+ next.addEventListener("click", handleNext);
5462
+ element.append(previous, input, position, next);
5463
+ return {
5464
+ element,
5465
+ setCurrent(page) {
5466
+ input.value = String(page);
5467
+ previous.disabled = page <= 1;
5468
+ next.disabled = page >= total;
5469
+ },
5470
+ destroy() {
5471
+ input.removeEventListener("change", commit);
5472
+ input.removeEventListener("keydown", handleKeydown);
5473
+ previous.removeEventListener("click", handlePrevious);
5474
+ next.removeEventListener("click", handleNext);
5475
+ }
5476
+ };
5325
5477
  }
5326
- function configurePdfWorker(pdf, workerSrc) {
5478
+ function configurePdfWorker(pdf, workerSrc, legacy = false) {
5327
5479
  if (workerSrc) {
5328
5480
  pdf.GlobalWorkerOptions.workerSrc = workerSrc;
5329
5481
  return;
5330
5482
  }
5331
5483
  if (!pdf.GlobalWorkerOptions.workerSrc && typeof window !== "undefined") {
5332
- pdf.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/build/pdf.worker.mjs`;
5484
+ const build = legacy ? "legacy/build" : "build";
5485
+ pdf.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/${build}/pdf.worker.mjs`;
5486
+ }
5487
+ }
5488
+ function shouldUseLegacyPdfCompatibility(mode = "auto") {
5489
+ if (mode === "legacy") {
5490
+ return true;
5491
+ }
5492
+ if (mode === "modern") {
5493
+ return false;
5494
+ }
5495
+ const promise = Promise;
5496
+ if (typeof promise.withResolvers !== "function") {
5497
+ return true;
5498
+ }
5499
+ if (typeof navigator === "undefined") {
5500
+ return false;
5333
5501
  }
5502
+ return /(?:QIHU|360SE|360EE)/i.test(navigator.userAgent);
5503
+ }
5504
+ function installPromiseWithResolversPolyfill() {
5505
+ const promise = Promise;
5506
+ if (typeof promise.withResolvers === "function") {
5507
+ return;
5508
+ }
5509
+ Object.defineProperty(promise, "withResolvers", {
5510
+ configurable: true,
5511
+ writable: true,
5512
+ value: () => {
5513
+ let resolve;
5514
+ let reject;
5515
+ const pending = new Promise((resolvePromise, rejectPromise) => {
5516
+ resolve = resolvePromise;
5517
+ reject = rejectPromise;
5518
+ });
5519
+ return { promise: pending, resolve, reject };
5520
+ }
5521
+ });
5334
5522
  }
5335
5523
 
5336
5524
  // src/plugins/epub.ts
@@ -7725,8 +7913,9 @@ async function renderConvertedOfficePreview(panel, ctx, options, conversionConte
7725
7913
  fit: ctx.options.fit,
7726
7914
  zoom: ctx.options.zoom,
7727
7915
  toolbar: ctx.toolbar,
7728
- title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
7729
- fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
7916
+ messages: ctx.options.messages,
7917
+ title: ctx.options.messages.officeConvertedTitle,
7918
+ fallbackTitle: ctx.options.messages.officeConvertedPdfFailed,
7730
7919
  revokeUrlOnDestroy: converted.revokeUrlOnDestroy
7731
7920
  });
7732
7921
  }