@open-file-viewer/core 0.1.11 → 0.1.12

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/dist/index.d.cts CHANGED
@@ -152,7 +152,26 @@ declare function epubPlugin(): PreviewPlugin;
152
152
 
153
153
  declare function xpsPlugin(): PreviewPlugin;
154
154
 
155
- declare function officePlugin(): PreviewPlugin;
155
+ interface OfficeConversionContext {
156
+ file: PreviewContext["file"];
157
+ arrayBuffer: ArrayBuffer;
158
+ extension: string;
159
+ detectedFormat?: "docx" | "xlsx" | "pptx";
160
+ reason: "complex-docx" | "legacy-office" | "manual";
161
+ }
162
+ type OfficeConversionResult = Blob | ArrayBuffer | string | {
163
+ blob?: Blob;
164
+ data?: Blob | ArrayBuffer;
165
+ url?: string;
166
+ fileName?: string;
167
+ mimeType?: string;
168
+ };
169
+ interface OfficePluginOptions {
170
+ convert?: (ctx: OfficeConversionContext) => Promise<OfficeConversionResult | null | undefined> | OfficeConversionResult | null | undefined;
171
+ preferConversion?: boolean | ((ctx: OfficeConversionContext) => boolean | Promise<boolean>);
172
+ pdf?: PdfPluginOptions;
173
+ }
174
+ declare function officePlugin(options?: OfficePluginOptions): PreviewPlugin;
156
175
 
157
176
  declare function ofdPlugin(): PreviewPlugin;
158
177
 
@@ -211,4 +230,4 @@ declare function assetPlugin(): PreviewPlugin;
211
230
 
212
231
  declare function fallbackPlugin(): PreviewPlugin;
213
232
 
214
- export { type CadBinaryPreviewContext, type CadPluginOptions, type FileViewer, type LibreDwgPreviewOptions, type PreviewCommand, type PreviewContext, type PreviewFallback, type PreviewFile, type PreviewFit, type PreviewInstance, type PreviewItem, type PreviewOptions, type PreviewPlugin, type PreviewSize, type PreviewSource, type PreviewTheme, type PreviewToolbarActionId, type PreviewToolbarBuiltInAction, type PreviewToolbarCustomAction, type PreviewToolbarOptions, type PreviewToolbarRenderContext, archivePlugin, assetPlugin, audioPlugin, cadPlugin, createViewer, drawingPlugin, emailPlugin, epubPlugin, fallbackPlugin, gisPlugin, imagePlugin, model3dPlugin, ofdPlugin, officePlugin, pdfPlugin, textPlugin, videoPlugin, xpsPlugin };
233
+ export { type CadBinaryPreviewContext, type CadPluginOptions, type FileViewer, type LibreDwgPreviewOptions, type OfficeConversionContext, type OfficeConversionResult, type OfficePluginOptions, type PreviewCommand, type PreviewContext, type PreviewFallback, type PreviewFile, type PreviewFit, type PreviewInstance, type PreviewItem, type PreviewOptions, type PreviewPlugin, type PreviewSize, type PreviewSource, type PreviewTheme, type PreviewToolbarActionId, type PreviewToolbarBuiltInAction, type PreviewToolbarCustomAction, type PreviewToolbarOptions, type PreviewToolbarRenderContext, archivePlugin, assetPlugin, audioPlugin, cadPlugin, createViewer, drawingPlugin, emailPlugin, epubPlugin, fallbackPlugin, gisPlugin, imagePlugin, model3dPlugin, ofdPlugin, officePlugin, pdfPlugin, textPlugin, videoPlugin, xpsPlugin };
package/dist/index.d.ts CHANGED
@@ -152,7 +152,26 @@ declare function epubPlugin(): PreviewPlugin;
152
152
 
153
153
  declare function xpsPlugin(): PreviewPlugin;
154
154
 
155
- declare function officePlugin(): PreviewPlugin;
155
+ interface OfficeConversionContext {
156
+ file: PreviewContext["file"];
157
+ arrayBuffer: ArrayBuffer;
158
+ extension: string;
159
+ detectedFormat?: "docx" | "xlsx" | "pptx";
160
+ reason: "complex-docx" | "legacy-office" | "manual";
161
+ }
162
+ type OfficeConversionResult = Blob | ArrayBuffer | string | {
163
+ blob?: Blob;
164
+ data?: Blob | ArrayBuffer;
165
+ url?: string;
166
+ fileName?: string;
167
+ mimeType?: string;
168
+ };
169
+ interface OfficePluginOptions {
170
+ convert?: (ctx: OfficeConversionContext) => Promise<OfficeConversionResult | null | undefined> | OfficeConversionResult | null | undefined;
171
+ preferConversion?: boolean | ((ctx: OfficeConversionContext) => boolean | Promise<boolean>);
172
+ pdf?: PdfPluginOptions;
173
+ }
174
+ declare function officePlugin(options?: OfficePluginOptions): PreviewPlugin;
156
175
 
157
176
  declare function ofdPlugin(): PreviewPlugin;
158
177
 
@@ -211,4 +230,4 @@ declare function assetPlugin(): PreviewPlugin;
211
230
 
212
231
  declare function fallbackPlugin(): PreviewPlugin;
213
232
 
214
- export { type CadBinaryPreviewContext, type CadPluginOptions, type FileViewer, type LibreDwgPreviewOptions, type PreviewCommand, type PreviewContext, type PreviewFallback, type PreviewFile, type PreviewFit, type PreviewInstance, type PreviewItem, type PreviewOptions, type PreviewPlugin, type PreviewSize, type PreviewSource, type PreviewTheme, type PreviewToolbarActionId, type PreviewToolbarBuiltInAction, type PreviewToolbarCustomAction, type PreviewToolbarOptions, type PreviewToolbarRenderContext, archivePlugin, assetPlugin, audioPlugin, cadPlugin, createViewer, drawingPlugin, emailPlugin, epubPlugin, fallbackPlugin, gisPlugin, imagePlugin, model3dPlugin, ofdPlugin, officePlugin, pdfPlugin, textPlugin, videoPlugin, xpsPlugin };
233
+ export { type CadBinaryPreviewContext, type CadPluginOptions, type FileViewer, type LibreDwgPreviewOptions, type OfficeConversionContext, type OfficeConversionResult, type OfficePluginOptions, type PreviewCommand, type PreviewContext, type PreviewFallback, type PreviewFile, type PreviewFit, type PreviewInstance, type PreviewItem, type PreviewOptions, type PreviewPlugin, type PreviewSize, type PreviewSource, type PreviewTheme, type PreviewToolbarActionId, type PreviewToolbarBuiltInAction, type PreviewToolbarCustomAction, type PreviewToolbarOptions, type PreviewToolbarRenderContext, archivePlugin, assetPlugin, audioPlugin, cadPlugin, createViewer, drawingPlugin, emailPlugin, epubPlugin, fallbackPlugin, gisPlugin, imagePlugin, model3dPlugin, ofdPlugin, officePlugin, pdfPlugin, textPlugin, videoPlugin, xpsPlugin };
package/dist/index.js CHANGED
@@ -5802,7 +5802,7 @@ var officeMimeFormatMap = {
5802
5802
  "application/vnd.oasis.opendocument.presentation-flat-xml": "fodp",
5803
5803
  "application/vnd.apple.keynote": "key"
5804
5804
  };
5805
- function officePlugin() {
5805
+ function officePlugin(options = {}) {
5806
5806
  return {
5807
5807
  name: "office",
5808
5808
  match(file) {
@@ -5815,7 +5815,11 @@ function officePlugin() {
5815
5815
  const arrayBuffer = await readArrayBuffer(ctx.file);
5816
5816
  const packageFormat = shouldSniffPackagedOffice(extension) ? await detectPackagedOfficeFormat(arrayBuffer) : void 0;
5817
5817
  let disposeDocxFit;
5818
- if (packageFormat === "docx" && !fileIsDocx(extension)) {
5818
+ let delegatedInstance;
5819
+ const conversionContext = await createOfficeConversionContext(ctx, arrayBuffer, extension, packageFormat);
5820
+ if (conversionContext && await shouldUseOfficeConversion(options, conversionContext)) {
5821
+ delegatedInstance = await renderConvertedOfficePreview(panel, ctx, options, conversionContext);
5822
+ } else if (packageFormat === "docx" && !fileIsDocx(extension)) {
5819
5823
  disposeDocxFit = await renderDocx(panel, arrayBuffer);
5820
5824
  } else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
5821
5825
  await renderSheet(panel, arrayBuffer, "xlsx");
@@ -5849,12 +5853,13 @@ function officePlugin() {
5849
5853
  ctx.toolbar?.refreshCommandSupport();
5850
5854
  return {
5851
5855
  canCommand(command) {
5852
- return controller?.canCommand(command) ?? false;
5856
+ return delegatedInstance?.canCommand?.(command) || controller?.canCommand(command) || false;
5853
5857
  },
5854
5858
  command(command) {
5855
- return controller?.command(command) ?? false;
5859
+ return delegatedInstance?.command?.(command) || controller?.command(command) || false;
5856
5860
  },
5857
5861
  destroy() {
5862
+ delegatedInstance?.destroy();
5858
5863
  controller?.destroy();
5859
5864
  disposeDocxFit?.();
5860
5865
  panel.remove();
@@ -5863,6 +5868,104 @@ function officePlugin() {
5863
5868
  }
5864
5869
  };
5865
5870
  }
5871
+ async function createOfficeConversionContext(ctx, arrayBuffer, extension, detectedFormat) {
5872
+ const effectiveFormat = detectedFormat || extension;
5873
+ if ((effectiveFormat === "docx" || fileIsDocx(extension)) && await docxShouldPreferTextboxLayoutFallback(arrayBuffer)) {
5874
+ return { file: ctx.file, arrayBuffer, extension, detectedFormat, reason: "complex-docx" };
5875
+ }
5876
+ if (isLegacyOfficeBinary(extension)) {
5877
+ return { file: ctx.file, arrayBuffer, extension, detectedFormat, reason: "legacy-office" };
5878
+ }
5879
+ return void 0;
5880
+ }
5881
+ async function shouldUseOfficeConversion(options, context) {
5882
+ if (!options.convert) {
5883
+ return false;
5884
+ }
5885
+ if (typeof options.preferConversion === "function") {
5886
+ return Boolean(await options.preferConversion(context));
5887
+ }
5888
+ if (options.preferConversion !== void 0) {
5889
+ return options.preferConversion;
5890
+ }
5891
+ return context.reason === "complex-docx" || context.reason === "legacy-office";
5892
+ }
5893
+ async function renderConvertedOfficePreview(panel, ctx, options, conversionContext) {
5894
+ if (!options.convert) {
5895
+ throw new Error("Office conversion handler is not configured.");
5896
+ }
5897
+ const converted = normalizeOfficeConversionResult(await options.convert(conversionContext), ctx.file.name);
5898
+ if (!converted) {
5899
+ throw new Error("Office conversion handler did not return a previewable file.");
5900
+ }
5901
+ if (converted.mimeType !== "application/pdf" && !converted.fileName.toLowerCase().endsWith(".pdf")) {
5902
+ throw new Error("Office conversion handler must return a PDF Blob, ArrayBuffer or URL.");
5903
+ }
5904
+ return renderPdfDocumentPreview({
5905
+ ...options.pdf || {},
5906
+ fileName: converted.fileName,
5907
+ fileUrl: converted.fileUrl,
5908
+ fileSize: converted.fileSize,
5909
+ isExternal: !converted.revokeUrlOnDestroy,
5910
+ viewport: panel,
5911
+ size: ctx.size,
5912
+ fit: ctx.options.fit,
5913
+ toolbar: ctx.toolbar,
5914
+ title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
5915
+ fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
5916
+ revokeUrlOnDestroy: converted.revokeUrlOnDestroy
5917
+ });
5918
+ }
5919
+ function normalizeOfficeConversionResult(result, sourceFileName) {
5920
+ if (!result) {
5921
+ return void 0;
5922
+ }
5923
+ const fallbackFileName = `${stripFileExtension(sourceFileName) || "office-preview"}.pdf`;
5924
+ if (typeof result === "string") {
5925
+ return {
5926
+ fileName: fallbackFileName,
5927
+ fileUrl: result,
5928
+ mimeType: "application/pdf",
5929
+ revokeUrlOnDestroy: false
5930
+ };
5931
+ }
5932
+ if (result instanceof ArrayBuffer) {
5933
+ const blob = new Blob([result], { type: "application/pdf" });
5934
+ return createConvertedOfficeBlobPreview(blob, fallbackFileName);
5935
+ }
5936
+ if (result instanceof Blob) {
5937
+ return createConvertedOfficeBlobPreview(result, fallbackFileName);
5938
+ }
5939
+ if (result.url) {
5940
+ return {
5941
+ fileName: result.fileName || fallbackFileName,
5942
+ fileUrl: result.url,
5943
+ mimeType: result.mimeType || "application/pdf",
5944
+ revokeUrlOnDestroy: false
5945
+ };
5946
+ }
5947
+ const data = result.blob || result.data;
5948
+ if (data instanceof ArrayBuffer) {
5949
+ const blob = new Blob([data], { type: result.mimeType || "application/pdf" });
5950
+ return createConvertedOfficeBlobPreview(blob, result.fileName || fallbackFileName);
5951
+ }
5952
+ if (data instanceof Blob) {
5953
+ return createConvertedOfficeBlobPreview(data, result.fileName || fallbackFileName);
5954
+ }
5955
+ return void 0;
5956
+ }
5957
+ function createConvertedOfficeBlobPreview(blob, fileName) {
5958
+ return {
5959
+ fileName,
5960
+ fileUrl: URL.createObjectURL(blob),
5961
+ fileSize: blob.size,
5962
+ mimeType: blob.type || "application/pdf",
5963
+ revokeUrlOnDestroy: true
5964
+ };
5965
+ }
5966
+ function stripFileExtension(fileName) {
5967
+ return fileName.replace(/\.[^.]+$/, "");
5968
+ }
5866
5969
  function createOfficeZoomController(panel, ctx) {
5867
5970
  const canZoom = Boolean(
5868
5971
  panel.querySelector(".ofv-docx-document, .ofv-sheet, .ofv-pptx-viewer > div, .ofv-document, .ofv-text-block, .ofv-slide")
@@ -5959,7 +6062,7 @@ async function renderDocx(panel, arrayBuffer) {
5959
6062
  useBase64URL: true
5960
6063
  });
5961
6064
  await normalizeDocxLayout(content, arrayBuffer);
5962
- const shouldUseTextboxFallback = await docxPreviewLooksBlank(content, arrayBuffer) || await docxPreviewMissesRichTextboxContent(content, arrayBuffer);
6065
+ const shouldUseTextboxFallback = await docxPreviewLooksBlank(content, arrayBuffer) || await docxPreviewMissesRichTextboxContent(content, arrayBuffer) || await docxShouldPreferTextboxLayoutFallback(arrayBuffer);
5963
6066
  if (shouldUseTextboxFallback) {
5964
6067
  disposeFit?.();
5965
6068
  styleContainer.remove();
@@ -6042,6 +6145,27 @@ async function docxHasRichTextboxContent(arrayBuffer) {
6042
6145
  return false;
6043
6146
  }
6044
6147
  }
6148
+ async function docxShouldPreferTextboxLayoutFallback(arrayBuffer) {
6149
+ try {
6150
+ const zip = await JSZip3.loadAsync(arrayBuffer);
6151
+ const documentXml = await zip.file("word/document.xml")?.async("text");
6152
+ if (!documentXml || !/\btxbxContent\b/.test(documentXml)) {
6153
+ return false;
6154
+ }
6155
+ const blocks = extractDocxTextboxBlocks(documentXml);
6156
+ const meaningfulBlocks = blocks.filter((block) => block.paragraphs.length > 0);
6157
+ const sidebarBackgrounds = blocks.filter(
6158
+ (block) => block.paragraphs.length === 0 && block.fill && block.relativeV === "page" && block.x < 0 && block.width >= 120 && block.height >= 500
6159
+ );
6160
+ const pageAnchoredTextboxes = meaningfulBlocks.filter((block) => block.relativeV === "page");
6161
+ const paragraphAnchoredTextboxes = meaningfulBlocks.filter((block) => block.relativeV !== "page");
6162
+ const leftTextboxes = meaningfulBlocks.filter((block) => block.x < 0);
6163
+ const rightTextboxes = meaningfulBlocks.filter((block) => block.x >= 80);
6164
+ return sidebarBackgrounds.length >= 2 && meaningfulBlocks.length >= 8 && pageAnchoredTextboxes.length >= 4 && paragraphAnchoredTextboxes.length >= 2 && leftTextboxes.length >= 3 && rightTextboxes.length >= 3;
6165
+ } catch {
6166
+ return false;
6167
+ }
6168
+ }
6045
6169
  async function renderDocxContentFallback(container, arrayBuffer, options = {}) {
6046
6170
  if (options.showNote !== false) {
6047
6171
  const fallbackNote = document.createElement("div");
@@ -6493,6 +6617,10 @@ function createDocxTextboxBlockElement(block) {
6493
6617
  const paragraphs = normalizeDocxTextboxParagraphOrder(block);
6494
6618
  const [first, ...rest] = paragraphs;
6495
6619
  if (first) {
6620
+ const sectionKind = getDocxTextboxSectionKind(first);
6621
+ if (sectionKind) {
6622
+ section.classList.add(`ofv-docx-textbox-section-${sectionKind}`);
6623
+ }
6496
6624
  const heading = document.createElement("h3");
6497
6625
  heading.textContent = first;
6498
6626
  section.append(heading);
@@ -6505,6 +6633,28 @@ function createDocxTextboxBlockElement(block) {
6505
6633
  }
6506
6634
  return section;
6507
6635
  }
6636
+ function getDocxTextboxSectionKind(heading) {
6637
+ const text = normalizePreviewText(heading);
6638
+ if (text.includes("\u6559\u80B2\u80CC\u666F")) {
6639
+ return "education";
6640
+ }
6641
+ if (text.includes("\u4E13\u4E1A\u6280\u80FD")) {
6642
+ return "skills";
6643
+ }
6644
+ if (text.includes("\u5DE5\u4F5C\u7ECF\u5386")) {
6645
+ return "work";
6646
+ }
6647
+ if (text.includes("\u9879\u76EE\u7ECF\u9A8C")) {
6648
+ return "projects";
6649
+ }
6650
+ if (text.includes("\u81EA\u6211\u8BC4\u4EF7")) {
6651
+ return "summary";
6652
+ }
6653
+ if (text.includes("\u57FA\u672C\u4FE1\u606F")) {
6654
+ return "profile";
6655
+ }
6656
+ return "";
6657
+ }
6508
6658
  function estimateDocxTextboxBlockHeight(block) {
6509
6659
  return Math.max(block.height, 18 + block.paragraphs.length * 14);
6510
6660
  }
@@ -6548,13 +6698,57 @@ function repairDocxHeadingShapeAlignment(page) {
6548
6698
  if (!svg) {
6549
6699
  continue;
6550
6700
  }
6551
- const width = parseCssPixelValue(svg.getAttribute("width") || svg.style.width);
6701
+ const width = parseCssPixelValue(svg.style.width) || parseCssPixelValue(svg.getAttribute("width") || "");
6552
6702
  const marginLeft = parseCssPixelValue(svg.style.marginLeft);
6553
6703
  if (width < 300 || marginLeft < 28 || marginLeft > 44) {
6554
6704
  continue;
6555
6705
  }
6556
- svg.style.marginLeft = "48px";
6706
+ const textWidth = getDocxParagraphVisibleTextWidth(paragraph);
6707
+ svg.style.marginLeft = `${formatCssNumber(Math.max(48, marginLeft + textWidth * 0.68))}pt`;
6708
+ svg.style.marginTop = `${formatCssNumber(parseCssPixelValue(svg.style.marginTop) - 4)}pt`;
6709
+ normalizeDocxHeadingShapeFill(svg);
6710
+ repairDocxHeadingTextBackground(paragraph);
6711
+ }
6712
+ }
6713
+ function normalizeDocxHeadingShapeFill(svg) {
6714
+ const headingFill = "#3f4aa3";
6715
+ const fillNodes = svg.querySelectorAll("image[fill], rect[data-ofv-docx-shape-fill]");
6716
+ for (const node of fillNodes) {
6717
+ const fill = node.getAttribute("fill") || "";
6718
+ if (fill.toLowerCase() === "#38449a") {
6719
+ node.setAttribute("fill", headingFill);
6720
+ }
6721
+ }
6722
+ }
6723
+ function repairDocxHeadingTextBackground(paragraph) {
6724
+ const textSpans = Array.from(paragraph.querySelectorAll("span")).filter(
6725
+ (element) => normalizePreviewText(element.textContent || "")
6726
+ );
6727
+ const lastTextSpan = textSpans.at(-1);
6728
+ if (!lastTextSpan || !hasWhiteBackground(lastTextSpan)) {
6729
+ return;
6730
+ }
6731
+ lastTextSpan.style.paddingRight = "3pt";
6732
+ lastTextSpan.style.paddingTop = "2pt";
6733
+ lastTextSpan.style.paddingBottom = "2pt";
6734
+ lastTextSpan.style.boxDecorationBreak = "clone";
6735
+ }
6736
+ function hasWhiteBackground(element) {
6737
+ const background = element.style.backgroundColor.replace(/\s+/g, "").toLowerCase();
6738
+ return background === "white" || background === "#fff" || background === "#ffffff" || background === "rgb(255,255,255)";
6739
+ }
6740
+ function getDocxParagraphVisibleTextWidth(paragraph) {
6741
+ let textWidth = 0;
6742
+ for (const element of paragraph.querySelectorAll("span")) {
6743
+ if (!normalizePreviewText(element.textContent || "")) {
6744
+ continue;
6745
+ }
6746
+ textWidth += pxToPt(element.getBoundingClientRect().width);
6557
6747
  }
6748
+ return textWidth;
6749
+ }
6750
+ function pxToPt(value) {
6751
+ return value * 0.75;
6558
6752
  }
6559
6753
  function repairDocxListIndentAlignment(page) {
6560
6754
  for (const paragraph of page.querySelectorAll("p[class*='ofv-docx-num-']")) {