@open-file-viewer/core 0.1.11 → 0.1.13
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 +38 -0
- package/dist/index.cjs +230 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +230 -12
- package/dist/index.js.map +1 -1
- package/dist/style.css +56 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,44 @@ 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
|
+
## High-Fidelity Office Conversion
|
|
127
|
+
|
|
128
|
+
Browser-side Office renderers cannot perfectly reproduce Word/WPS layout for files with anchored
|
|
129
|
+
textboxes, absolute positioning, custom fonts, headers/footers or legacy binary formats. For those
|
|
130
|
+
files, configure `officePlugin({ convert })` to send the file to your own LibreOffice, OnlyOffice or
|
|
131
|
+
Microsoft Graph conversion service and return a PDF. The converted PDF is rendered by the built-in
|
|
132
|
+
PDF viewer.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
officePlugin({
|
|
136
|
+
pdf: { workerSrc: pdfWorkerSrc },
|
|
137
|
+
async convert({ file, arrayBuffer, reason }) {
|
|
138
|
+
const form = new FormData();
|
|
139
|
+
form.append("file", new Blob([arrayBuffer]), file.name);
|
|
140
|
+
form.append("reason", reason);
|
|
141
|
+
|
|
142
|
+
const response = await fetch("/api/office/convert-to-pdf", {
|
|
143
|
+
method: "POST",
|
|
144
|
+
body: form
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new Error("Office conversion failed");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
blob: await response.blob(),
|
|
153
|
+
fileName: file.name.replace(/\.[^.]+$/, ".pdf"),
|
|
154
|
+
mimeType: "application/pdf"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Open File Viewer does not upload files by default. The conversion hook is only called when you
|
|
161
|
+
explicitly configure it, and currently targets `complex-docx` and `legacy-office` cases. You can also
|
|
162
|
+
return `{ url, fileName, mimeType: "application/pdf" }` when your service stores the converted PDF.
|
|
163
|
+
|
|
126
164
|
## CAD Customization
|
|
127
165
|
|
|
128
166
|
`cadPlugin()` has two CAD preview layers:
|
package/dist/index.cjs
CHANGED
|
@@ -735,7 +735,7 @@ function createViewer(options) {
|
|
|
735
735
|
if (destroyed || token !== renderToken) {
|
|
736
736
|
return;
|
|
737
737
|
}
|
|
738
|
-
currentInstance
|
|
738
|
+
destroyPreviewInstance(currentInstance);
|
|
739
739
|
currentInstance = void 0;
|
|
740
740
|
viewport.replaceChildren();
|
|
741
741
|
setLoading(true);
|
|
@@ -757,7 +757,7 @@ function createViewer(options) {
|
|
|
757
757
|
setError
|
|
758
758
|
});
|
|
759
759
|
if (destroyed || token !== renderToken) {
|
|
760
|
-
nextInstance
|
|
760
|
+
destroyPreviewInstance(nextInstance);
|
|
761
761
|
return;
|
|
762
762
|
}
|
|
763
763
|
currentInstance = nextInstance;
|
|
@@ -814,7 +814,7 @@ function createViewer(options) {
|
|
|
814
814
|
destroyed = true;
|
|
815
815
|
renderToken += 1;
|
|
816
816
|
resizeObserver.destroy();
|
|
817
|
-
currentInstance
|
|
817
|
+
destroyPreviewInstance(currentInstance);
|
|
818
818
|
toolbar?.destroy();
|
|
819
819
|
theme.destroy();
|
|
820
820
|
container.replaceChildren();
|
|
@@ -825,6 +825,16 @@ function createViewer(options) {
|
|
|
825
825
|
}
|
|
826
826
|
};
|
|
827
827
|
}
|
|
828
|
+
function destroyPreviewInstance(instance) {
|
|
829
|
+
if (!instance) {
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
try {
|
|
833
|
+
instance.destroy();
|
|
834
|
+
} catch (error) {
|
|
835
|
+
console.error("Failed to destroy file preview instance:", error);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
828
838
|
function normalizeQueue(options) {
|
|
829
839
|
if (options.files && options.files.length > 0) {
|
|
830
840
|
return options.files.map(
|
|
@@ -4628,7 +4638,7 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4628
4638
|
},
|
|
4629
4639
|
destroy() {
|
|
4630
4640
|
options.viewport.classList.remove("ofv-center");
|
|
4631
|
-
documentTask
|
|
4641
|
+
destroyPdfResource(documentTask);
|
|
4632
4642
|
if (options.revokeUrlOnDestroy) {
|
|
4633
4643
|
revokeObjectUrl(options.fileUrl, Boolean(options.isExternal));
|
|
4634
4644
|
}
|
|
@@ -4861,13 +4871,27 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4861
4871
|
}
|
|
4862
4872
|
});
|
|
4863
4873
|
pageStates.length = 0;
|
|
4864
|
-
|
|
4874
|
+
destroyPdfResource(pdfDocument);
|
|
4875
|
+
destroyPdfResource(documentTask);
|
|
4865
4876
|
if (options.revokeUrlOnDestroy) {
|
|
4866
4877
|
revokeObjectUrl(options.fileUrl, Boolean(options.isExternal));
|
|
4867
4878
|
}
|
|
4868
4879
|
}
|
|
4869
4880
|
};
|
|
4870
4881
|
}
|
|
4882
|
+
function destroyPdfResource(resource) {
|
|
4883
|
+
if (!resource || typeof resource !== "object") {
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
const candidate = resource;
|
|
4887
|
+
if (typeof candidate.destroy === "function") {
|
|
4888
|
+
void candidate.destroy();
|
|
4889
|
+
return;
|
|
4890
|
+
}
|
|
4891
|
+
if (typeof candidate.cleanup === "function") {
|
|
4892
|
+
void candidate.cleanup();
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4871
4895
|
function getPdfOutputScale() {
|
|
4872
4896
|
if (typeof window === "undefined") {
|
|
4873
4897
|
return 1;
|
|
@@ -5855,7 +5879,7 @@ var officeMimeFormatMap = {
|
|
|
5855
5879
|
"application/vnd.oasis.opendocument.presentation-flat-xml": "fodp",
|
|
5856
5880
|
"application/vnd.apple.keynote": "key"
|
|
5857
5881
|
};
|
|
5858
|
-
function officePlugin() {
|
|
5882
|
+
function officePlugin(options = {}) {
|
|
5859
5883
|
return {
|
|
5860
5884
|
name: "office",
|
|
5861
5885
|
match(file) {
|
|
@@ -5868,7 +5892,11 @@ function officePlugin() {
|
|
|
5868
5892
|
const arrayBuffer = await readArrayBuffer(ctx.file);
|
|
5869
5893
|
const packageFormat = shouldSniffPackagedOffice(extension) ? await detectPackagedOfficeFormat(arrayBuffer) : void 0;
|
|
5870
5894
|
let disposeDocxFit;
|
|
5871
|
-
|
|
5895
|
+
let delegatedInstance;
|
|
5896
|
+
const conversionContext = await createOfficeConversionContext(ctx, arrayBuffer, extension, packageFormat);
|
|
5897
|
+
if (conversionContext && await shouldUseOfficeConversion(options, conversionContext)) {
|
|
5898
|
+
delegatedInstance = await renderConvertedOfficePreview(panel, ctx, options, conversionContext);
|
|
5899
|
+
} else if (packageFormat === "docx" && !fileIsDocx(extension)) {
|
|
5872
5900
|
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5873
5901
|
} else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
|
|
5874
5902
|
await renderSheet(panel, arrayBuffer, "xlsx");
|
|
@@ -5902,12 +5930,13 @@ function officePlugin() {
|
|
|
5902
5930
|
ctx.toolbar?.refreshCommandSupport();
|
|
5903
5931
|
return {
|
|
5904
5932
|
canCommand(command) {
|
|
5905
|
-
return controller?.canCommand(command)
|
|
5933
|
+
return delegatedInstance?.canCommand?.(command) || controller?.canCommand(command) || false;
|
|
5906
5934
|
},
|
|
5907
5935
|
command(command) {
|
|
5908
|
-
return controller?.command(command)
|
|
5936
|
+
return delegatedInstance?.command?.(command) || controller?.command(command) || false;
|
|
5909
5937
|
},
|
|
5910
5938
|
destroy() {
|
|
5939
|
+
delegatedInstance?.destroy();
|
|
5911
5940
|
controller?.destroy();
|
|
5912
5941
|
disposeDocxFit?.();
|
|
5913
5942
|
panel.remove();
|
|
@@ -5916,6 +5945,104 @@ function officePlugin() {
|
|
|
5916
5945
|
}
|
|
5917
5946
|
};
|
|
5918
5947
|
}
|
|
5948
|
+
async function createOfficeConversionContext(ctx, arrayBuffer, extension, detectedFormat) {
|
|
5949
|
+
const effectiveFormat = detectedFormat || extension;
|
|
5950
|
+
if ((effectiveFormat === "docx" || fileIsDocx(extension)) && await docxShouldPreferTextboxLayoutFallback(arrayBuffer)) {
|
|
5951
|
+
return { file: ctx.file, arrayBuffer, extension, detectedFormat, reason: "complex-docx" };
|
|
5952
|
+
}
|
|
5953
|
+
if (isLegacyOfficeBinary(extension)) {
|
|
5954
|
+
return { file: ctx.file, arrayBuffer, extension, detectedFormat, reason: "legacy-office" };
|
|
5955
|
+
}
|
|
5956
|
+
return void 0;
|
|
5957
|
+
}
|
|
5958
|
+
async function shouldUseOfficeConversion(options, context) {
|
|
5959
|
+
if (!options.convert) {
|
|
5960
|
+
return false;
|
|
5961
|
+
}
|
|
5962
|
+
if (typeof options.preferConversion === "function") {
|
|
5963
|
+
return Boolean(await options.preferConversion(context));
|
|
5964
|
+
}
|
|
5965
|
+
if (options.preferConversion !== void 0) {
|
|
5966
|
+
return options.preferConversion;
|
|
5967
|
+
}
|
|
5968
|
+
return context.reason === "complex-docx" || context.reason === "legacy-office";
|
|
5969
|
+
}
|
|
5970
|
+
async function renderConvertedOfficePreview(panel, ctx, options, conversionContext) {
|
|
5971
|
+
if (!options.convert) {
|
|
5972
|
+
throw new Error("Office conversion handler is not configured.");
|
|
5973
|
+
}
|
|
5974
|
+
const converted = normalizeOfficeConversionResult(await options.convert(conversionContext), ctx.file.name);
|
|
5975
|
+
if (!converted) {
|
|
5976
|
+
throw new Error("Office conversion handler did not return a previewable file.");
|
|
5977
|
+
}
|
|
5978
|
+
if (converted.mimeType !== "application/pdf" && !converted.fileName.toLowerCase().endsWith(".pdf")) {
|
|
5979
|
+
throw new Error("Office conversion handler must return a PDF Blob, ArrayBuffer or URL.");
|
|
5980
|
+
}
|
|
5981
|
+
return renderPdfDocumentPreview({
|
|
5982
|
+
...options.pdf || {},
|
|
5983
|
+
fileName: converted.fileName,
|
|
5984
|
+
fileUrl: converted.fileUrl,
|
|
5985
|
+
fileSize: converted.fileSize,
|
|
5986
|
+
isExternal: !converted.revokeUrlOnDestroy,
|
|
5987
|
+
viewport: panel,
|
|
5988
|
+
size: ctx.size,
|
|
5989
|
+
fit: ctx.options.fit,
|
|
5990
|
+
toolbar: ctx.toolbar,
|
|
5991
|
+
title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
|
|
5992
|
+
fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
|
|
5993
|
+
revokeUrlOnDestroy: converted.revokeUrlOnDestroy
|
|
5994
|
+
});
|
|
5995
|
+
}
|
|
5996
|
+
function normalizeOfficeConversionResult(result, sourceFileName) {
|
|
5997
|
+
if (!result) {
|
|
5998
|
+
return void 0;
|
|
5999
|
+
}
|
|
6000
|
+
const fallbackFileName = `${stripFileExtension(sourceFileName) || "office-preview"}.pdf`;
|
|
6001
|
+
if (typeof result === "string") {
|
|
6002
|
+
return {
|
|
6003
|
+
fileName: fallbackFileName,
|
|
6004
|
+
fileUrl: result,
|
|
6005
|
+
mimeType: "application/pdf",
|
|
6006
|
+
revokeUrlOnDestroy: false
|
|
6007
|
+
};
|
|
6008
|
+
}
|
|
6009
|
+
if (result instanceof ArrayBuffer) {
|
|
6010
|
+
const blob = new Blob([result], { type: "application/pdf" });
|
|
6011
|
+
return createConvertedOfficeBlobPreview(blob, fallbackFileName);
|
|
6012
|
+
}
|
|
6013
|
+
if (result instanceof Blob) {
|
|
6014
|
+
return createConvertedOfficeBlobPreview(result, fallbackFileName);
|
|
6015
|
+
}
|
|
6016
|
+
if (result.url) {
|
|
6017
|
+
return {
|
|
6018
|
+
fileName: result.fileName || fallbackFileName,
|
|
6019
|
+
fileUrl: result.url,
|
|
6020
|
+
mimeType: result.mimeType || "application/pdf",
|
|
6021
|
+
revokeUrlOnDestroy: false
|
|
6022
|
+
};
|
|
6023
|
+
}
|
|
6024
|
+
const data = result.blob || result.data;
|
|
6025
|
+
if (data instanceof ArrayBuffer) {
|
|
6026
|
+
const blob = new Blob([data], { type: result.mimeType || "application/pdf" });
|
|
6027
|
+
return createConvertedOfficeBlobPreview(blob, result.fileName || fallbackFileName);
|
|
6028
|
+
}
|
|
6029
|
+
if (data instanceof Blob) {
|
|
6030
|
+
return createConvertedOfficeBlobPreview(data, result.fileName || fallbackFileName);
|
|
6031
|
+
}
|
|
6032
|
+
return void 0;
|
|
6033
|
+
}
|
|
6034
|
+
function createConvertedOfficeBlobPreview(blob, fileName) {
|
|
6035
|
+
return {
|
|
6036
|
+
fileName,
|
|
6037
|
+
fileUrl: URL.createObjectURL(blob),
|
|
6038
|
+
fileSize: blob.size,
|
|
6039
|
+
mimeType: blob.type || "application/pdf",
|
|
6040
|
+
revokeUrlOnDestroy: true
|
|
6041
|
+
};
|
|
6042
|
+
}
|
|
6043
|
+
function stripFileExtension(fileName) {
|
|
6044
|
+
return fileName.replace(/\.[^.]+$/, "");
|
|
6045
|
+
}
|
|
5919
6046
|
function createOfficeZoomController(panel, ctx) {
|
|
5920
6047
|
const canZoom = Boolean(
|
|
5921
6048
|
panel.querySelector(".ofv-docx-document, .ofv-sheet, .ofv-pptx-viewer > div, .ofv-document, .ofv-text-block, .ofv-slide")
|
|
@@ -6012,7 +6139,7 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
6012
6139
|
useBase64URL: true
|
|
6013
6140
|
});
|
|
6014
6141
|
await normalizeDocxLayout(content, arrayBuffer);
|
|
6015
|
-
const shouldUseTextboxFallback = await docxPreviewLooksBlank(content, arrayBuffer) || await docxPreviewMissesRichTextboxContent(content, arrayBuffer);
|
|
6142
|
+
const shouldUseTextboxFallback = await docxPreviewLooksBlank(content, arrayBuffer) || await docxPreviewMissesRichTextboxContent(content, arrayBuffer) || await docxShouldPreferTextboxLayoutFallback(arrayBuffer);
|
|
6016
6143
|
if (shouldUseTextboxFallback) {
|
|
6017
6144
|
disposeFit?.();
|
|
6018
6145
|
styleContainer.remove();
|
|
@@ -6095,6 +6222,27 @@ async function docxHasRichTextboxContent(arrayBuffer) {
|
|
|
6095
6222
|
return false;
|
|
6096
6223
|
}
|
|
6097
6224
|
}
|
|
6225
|
+
async function docxShouldPreferTextboxLayoutFallback(arrayBuffer) {
|
|
6226
|
+
try {
|
|
6227
|
+
const zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
6228
|
+
const documentXml = await zip.file("word/document.xml")?.async("text");
|
|
6229
|
+
if (!documentXml || !/\btxbxContent\b/.test(documentXml)) {
|
|
6230
|
+
return false;
|
|
6231
|
+
}
|
|
6232
|
+
const blocks = extractDocxTextboxBlocks(documentXml);
|
|
6233
|
+
const meaningfulBlocks = blocks.filter((block) => block.paragraphs.length > 0);
|
|
6234
|
+
const sidebarBackgrounds = blocks.filter(
|
|
6235
|
+
(block) => block.paragraphs.length === 0 && block.fill && block.relativeV === "page" && block.x < 0 && block.width >= 120 && block.height >= 500
|
|
6236
|
+
);
|
|
6237
|
+
const pageAnchoredTextboxes = meaningfulBlocks.filter((block) => block.relativeV === "page");
|
|
6238
|
+
const paragraphAnchoredTextboxes = meaningfulBlocks.filter((block) => block.relativeV !== "page");
|
|
6239
|
+
const leftTextboxes = meaningfulBlocks.filter((block) => block.x < 0);
|
|
6240
|
+
const rightTextboxes = meaningfulBlocks.filter((block) => block.x >= 80);
|
|
6241
|
+
return sidebarBackgrounds.length >= 2 && meaningfulBlocks.length >= 8 && pageAnchoredTextboxes.length >= 4 && paragraphAnchoredTextboxes.length >= 2 && leftTextboxes.length >= 3 && rightTextboxes.length >= 3;
|
|
6242
|
+
} catch {
|
|
6243
|
+
return false;
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6098
6246
|
async function renderDocxContentFallback(container, arrayBuffer, options = {}) {
|
|
6099
6247
|
if (options.showNote !== false) {
|
|
6100
6248
|
const fallbackNote = document.createElement("div");
|
|
@@ -6546,6 +6694,10 @@ function createDocxTextboxBlockElement(block) {
|
|
|
6546
6694
|
const paragraphs = normalizeDocxTextboxParagraphOrder(block);
|
|
6547
6695
|
const [first, ...rest] = paragraphs;
|
|
6548
6696
|
if (first) {
|
|
6697
|
+
const sectionKind = getDocxTextboxSectionKind(first);
|
|
6698
|
+
if (sectionKind) {
|
|
6699
|
+
section.classList.add(`ofv-docx-textbox-section-${sectionKind}`);
|
|
6700
|
+
}
|
|
6549
6701
|
const heading = document.createElement("h3");
|
|
6550
6702
|
heading.textContent = first;
|
|
6551
6703
|
section.append(heading);
|
|
@@ -6558,6 +6710,28 @@ function createDocxTextboxBlockElement(block) {
|
|
|
6558
6710
|
}
|
|
6559
6711
|
return section;
|
|
6560
6712
|
}
|
|
6713
|
+
function getDocxTextboxSectionKind(heading) {
|
|
6714
|
+
const text = normalizePreviewText(heading);
|
|
6715
|
+
if (text.includes("\u6559\u80B2\u80CC\u666F")) {
|
|
6716
|
+
return "education";
|
|
6717
|
+
}
|
|
6718
|
+
if (text.includes("\u4E13\u4E1A\u6280\u80FD")) {
|
|
6719
|
+
return "skills";
|
|
6720
|
+
}
|
|
6721
|
+
if (text.includes("\u5DE5\u4F5C\u7ECF\u5386")) {
|
|
6722
|
+
return "work";
|
|
6723
|
+
}
|
|
6724
|
+
if (text.includes("\u9879\u76EE\u7ECF\u9A8C")) {
|
|
6725
|
+
return "projects";
|
|
6726
|
+
}
|
|
6727
|
+
if (text.includes("\u81EA\u6211\u8BC4\u4EF7")) {
|
|
6728
|
+
return "summary";
|
|
6729
|
+
}
|
|
6730
|
+
if (text.includes("\u57FA\u672C\u4FE1\u606F")) {
|
|
6731
|
+
return "profile";
|
|
6732
|
+
}
|
|
6733
|
+
return "";
|
|
6734
|
+
}
|
|
6561
6735
|
function estimateDocxTextboxBlockHeight(block) {
|
|
6562
6736
|
return Math.max(block.height, 18 + block.paragraphs.length * 14);
|
|
6563
6737
|
}
|
|
@@ -6601,14 +6775,58 @@ function repairDocxHeadingShapeAlignment(page) {
|
|
|
6601
6775
|
if (!svg) {
|
|
6602
6776
|
continue;
|
|
6603
6777
|
}
|
|
6604
|
-
const width = parseCssPixelValue(svg.getAttribute("width") ||
|
|
6778
|
+
const width = parseCssPixelValue(svg.style.width) || parseCssPixelValue(svg.getAttribute("width") || "");
|
|
6605
6779
|
const marginLeft = parseCssPixelValue(svg.style.marginLeft);
|
|
6606
6780
|
if (width < 300 || marginLeft < 28 || marginLeft > 44) {
|
|
6607
6781
|
continue;
|
|
6608
6782
|
}
|
|
6609
|
-
|
|
6783
|
+
const textWidth = getDocxParagraphVisibleTextWidth(paragraph);
|
|
6784
|
+
svg.style.marginLeft = `${formatCssNumber(Math.max(48, marginLeft + textWidth * 0.68))}pt`;
|
|
6785
|
+
svg.style.marginTop = `${formatCssNumber(parseCssPixelValue(svg.style.marginTop) - 4)}pt`;
|
|
6786
|
+
normalizeDocxHeadingShapeFill(svg);
|
|
6787
|
+
repairDocxHeadingTextBackground(paragraph);
|
|
6610
6788
|
}
|
|
6611
6789
|
}
|
|
6790
|
+
function normalizeDocxHeadingShapeFill(svg) {
|
|
6791
|
+
const headingFill = "#3f4aa3";
|
|
6792
|
+
const fillNodes = svg.querySelectorAll("image[fill], rect[data-ofv-docx-shape-fill]");
|
|
6793
|
+
for (const node of fillNodes) {
|
|
6794
|
+
const fill = node.getAttribute("fill") || "";
|
|
6795
|
+
if (fill.toLowerCase() === "#38449a") {
|
|
6796
|
+
node.setAttribute("fill", headingFill);
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
function repairDocxHeadingTextBackground(paragraph) {
|
|
6801
|
+
const textSpans = Array.from(paragraph.querySelectorAll("span")).filter(
|
|
6802
|
+
(element) => normalizePreviewText(element.textContent || "")
|
|
6803
|
+
);
|
|
6804
|
+
const lastTextSpan = textSpans.at(-1);
|
|
6805
|
+
if (!lastTextSpan || !hasWhiteBackground(lastTextSpan)) {
|
|
6806
|
+
return;
|
|
6807
|
+
}
|
|
6808
|
+
lastTextSpan.style.paddingRight = "3pt";
|
|
6809
|
+
lastTextSpan.style.paddingTop = "2pt";
|
|
6810
|
+
lastTextSpan.style.paddingBottom = "2pt";
|
|
6811
|
+
lastTextSpan.style.boxDecorationBreak = "clone";
|
|
6812
|
+
}
|
|
6813
|
+
function hasWhiteBackground(element) {
|
|
6814
|
+
const background = element.style.backgroundColor.replace(/\s+/g, "").toLowerCase();
|
|
6815
|
+
return background === "white" || background === "#fff" || background === "#ffffff" || background === "rgb(255,255,255)";
|
|
6816
|
+
}
|
|
6817
|
+
function getDocxParagraphVisibleTextWidth(paragraph) {
|
|
6818
|
+
let textWidth = 0;
|
|
6819
|
+
for (const element of paragraph.querySelectorAll("span")) {
|
|
6820
|
+
if (!normalizePreviewText(element.textContent || "")) {
|
|
6821
|
+
continue;
|
|
6822
|
+
}
|
|
6823
|
+
textWidth += pxToPt(element.getBoundingClientRect().width);
|
|
6824
|
+
}
|
|
6825
|
+
return textWidth;
|
|
6826
|
+
}
|
|
6827
|
+
function pxToPt(value) {
|
|
6828
|
+
return value * 0.75;
|
|
6829
|
+
}
|
|
6612
6830
|
function repairDocxListIndentAlignment(page) {
|
|
6613
6831
|
for (const paragraph of page.querySelectorAll("p[class*='ofv-docx-num-']")) {
|
|
6614
6832
|
const text = normalizePreviewText(paragraph.textContent || "");
|