@cj-tech-master/excelts 9.6.1 → 10.0.0
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 +18 -3
- package/README_zh.md +18 -3
- package/dist/browser/modules/excel/cell.d.ts +4 -0
- package/dist/browser/modules/excel/note.js +5 -1
- package/dist/browser/modules/excel/row.js +35 -2
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/browser/modules/excel/types.d.ts +81 -0
- package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/browser/modules/excel/workbook.browser.js +32 -2
- package/dist/browser/modules/excel/worksheet.d.ts +31 -1
- package/dist/browser/modules/excel/worksheet.js +83 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
- package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/browser/modules/pdf/font/font-manager.js +35 -18
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
- package/dist/browser/modules/word/advanced/field-engine.js +45 -20
- package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/browser/modules/word/advanced/glossary.js +8 -9
- package/dist/browser/modules/word/advanced/math-convert.js +94 -12
- package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
- package/dist/browser/modules/word/advanced/style-map.js +31 -10
- package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/browser/modules/word/builder/run-builders.js +7 -1
- package/dist/browser/modules/word/constants.d.ts +4 -0
- package/dist/browser/modules/word/constants.js +5 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/browser/modules/word/convert/html/html-import.js +167 -14
- package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
- package/dist/browser/modules/word/convert/html/html.js +1 -1
- package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/browser/modules/word/convert/odt/odt.js +407 -56
- package/dist/browser/modules/word/html.d.ts +2 -2
- package/dist/browser/modules/word/html.js +1 -1
- package/dist/browser/modules/word/index.base.d.ts +3 -3
- package/dist/browser/modules/word/index.base.js +1 -1
- package/dist/browser/modules/word/layout/layout-full.js +326 -19
- package/dist/browser/modules/word/layout/render-page.js +35 -8
- package/dist/browser/modules/word/markdown.d.ts +1 -1
- package/dist/browser/modules/word/query/compat.d.ts +10 -2
- package/dist/browser/modules/word/query/compat.js +29 -21
- package/dist/browser/modules/word/reader/docx-reader.js +105 -2
- package/dist/browser/modules/word/reader/math-parser.js +8 -2
- package/dist/browser/modules/word/security/cfb-reader.js +5 -5
- package/dist/browser/modules/word/types.d.ts +96 -1
- package/dist/browser/modules/word/writer/docx-packager.js +108 -2
- package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
- package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/browser/modules/word/writer/math-writer.js +7 -2
- package/dist/browser/utils/font-metrics.d.ts +8 -0
- package/dist/browser/utils/font-metrics.js +43 -0
- package/dist/browser/utils/theme-colors.js +4 -1
- package/dist/cjs/modules/excel/note.js +5 -1
- package/dist/cjs/modules/excel/row.js +35 -2
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/cjs/modules/excel/workbook.browser.js +31 -1
- package/dist/cjs/modules/excel/worksheet.js +83 -0
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
- package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
- package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
- package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
- package/dist/cjs/modules/word/advanced/glossary.js +8 -9
- package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
- package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
- package/dist/cjs/modules/word/advanced/style-map.js +31 -10
- package/dist/cjs/modules/word/builder/run-builders.js +7 -1
- package/dist/cjs/modules/word/constants.js +5 -1
- package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
- package/dist/cjs/modules/word/convert/html/html.js +2 -1
- package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
- package/dist/cjs/modules/word/html.js +2 -1
- package/dist/cjs/modules/word/index.base.js +4 -3
- package/dist/cjs/modules/word/layout/layout-full.js +325 -18
- package/dist/cjs/modules/word/layout/render-page.js +35 -8
- package/dist/cjs/modules/word/query/compat.js +29 -21
- package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
- package/dist/cjs/modules/word/reader/math-parser.js +8 -2
- package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
- package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
- package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
- package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/cjs/modules/word/writer/math-writer.js +7 -2
- package/dist/cjs/utils/font-metrics.js +44 -0
- package/dist/cjs/utils/theme-colors.js +4 -1
- package/dist/esm/modules/excel/note.js +5 -1
- package/dist/esm/modules/excel/row.js +35 -2
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/esm/modules/excel/workbook.browser.js +32 -2
- package/dist/esm/modules/excel/worksheet.js +83 -0
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
- package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/esm/modules/pdf/font/font-manager.js +35 -18
- package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
- package/dist/esm/modules/word/advanced/field-engine.js +45 -20
- package/dist/esm/modules/word/advanced/glossary.js +8 -9
- package/dist/esm/modules/word/advanced/math-convert.js +94 -12
- package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
- package/dist/esm/modules/word/advanced/style-map.js +31 -10
- package/dist/esm/modules/word/builder/run-builders.js +7 -1
- package/dist/esm/modules/word/constants.js +5 -1
- package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/esm/modules/word/convert/html/html-import.js +167 -14
- package/dist/esm/modules/word/convert/html/html.js +1 -1
- package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/esm/modules/word/convert/odt/odt.js +407 -56
- package/dist/esm/modules/word/html.js +1 -1
- package/dist/esm/modules/word/index.base.js +1 -1
- package/dist/esm/modules/word/layout/layout-full.js +326 -19
- package/dist/esm/modules/word/layout/render-page.js +35 -8
- package/dist/esm/modules/word/query/compat.js +29 -21
- package/dist/esm/modules/word/reader/docx-reader.js +105 -2
- package/dist/esm/modules/word/reader/math-parser.js +8 -2
- package/dist/esm/modules/word/security/cfb-reader.js +5 -5
- package/dist/esm/modules/word/writer/docx-packager.js +108 -2
- package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
- package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/esm/modules/word/writer/math-writer.js +7 -2
- package/dist/esm/utils/font-metrics.js +43 -0
- package/dist/esm/utils/theme-colors.js +4 -1
- package/dist/iife/excelts.iife.js +496 -59
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +39 -39
- package/dist/types/modules/excel/cell.d.ts +4 -0
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/types/modules/excel/types.d.ts +81 -0
- package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/types/modules/excel/worksheet.d.ts +31 -1
- package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/types/modules/word/constants.d.ts +4 -0
- package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/types/modules/word/convert/html/html.d.ts +2 -2
- package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/types/modules/word/html.d.ts +2 -2
- package/dist/types/modules/word/index.base.d.ts +3 -3
- package/dist/types/modules/word/markdown.d.ts +1 -1
- package/dist/types/modules/word/query/compat.d.ts +10 -2
- package/dist/types/modules/word/types.d.ts +96 -1
- package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/types/utils/font-metrics.d.ts +8 -0
- package/package.json +3 -1
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.renderPage = renderPage;
|
|
15
15
|
exports.emitTextWithMatrix = emitTextWithMatrix;
|
|
16
|
+
exports.emitTextBlock = emitTextBlock;
|
|
16
17
|
exports.alphaGsName = alphaGsName;
|
|
17
18
|
exports.computeTextStartY = computeTextStartY;
|
|
18
19
|
exports.computeTextX = computeTextX;
|
|
@@ -684,7 +685,7 @@ function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, font
|
|
|
684
685
|
emitTextWithMatrix(stream, line, cos, sin, -sin, cos, tx, ty, resourceName, fontSize, fontManager, useType3);
|
|
685
686
|
}
|
|
686
687
|
}
|
|
687
|
-
/** Emit a text string with hex encoding if available. */
|
|
688
|
+
/** Emit a text string with hex encoding if available, onto a sink stream. */
|
|
688
689
|
function emitText(stream, fontManager, text, resourceName) {
|
|
689
690
|
const hex = fontManager.encodeText(text, resourceName);
|
|
690
691
|
if (hex) {
|
|
@@ -699,40 +700,133 @@ function emitText(stream, fontManager, text, resourceName) {
|
|
|
699
700
|
* when needed. For each sub-run the matrix origin is advanced along the
|
|
700
701
|
* text direction (cos, sin) by the rendered width.
|
|
701
702
|
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
703
|
+
* The emitted operators are written as a *deferred* fragment (see
|
|
704
|
+
* `PdfContentStream.deferred`). The fragment is only evaluated at
|
|
705
|
+
* serialization time, by which point `PdfDocumentBuilder.build()` has
|
|
706
|
+
* finalised the document's fonts (auto-discovered embedded CIDFont,
|
|
707
|
+
* Type3 fallback, or plain Type1). This is essential: at draw time the
|
|
708
|
+
* font manager has not yet decided whether a non-WinAnsi code point (e.g.
|
|
709
|
+
* U+2192 →) will be served by an embedded font or a Type3 glyph, so eager
|
|
710
|
+
* encoding would irreversibly degrade those characters to spaces via the
|
|
711
|
+
* WinAnsi fallback. Deferring the encode keeps the fragment at its exact
|
|
712
|
+
* draw-order slot (preserving z-order) while choosing the correct bytes
|
|
713
|
+
* once fonts are known.
|
|
714
|
+
*
|
|
715
|
+
* The `useType3` argument is the caller's *draw-time* guess and is ignored;
|
|
716
|
+
* the deferred body recomputes the routing from the now-settled font
|
|
717
|
+
* manager state.
|
|
718
|
+
*/
|
|
719
|
+
function emitTextWithMatrix(stream, text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager, _useType3) {
|
|
720
|
+
stream.deferred(() => renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager));
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Emit a text block as a single *deferred* fragment so that anchor
|
|
724
|
+
* alignment, word wrapping, and glyph encoding are all computed at
|
|
725
|
+
* serialization time — after `PdfDocumentBuilder.build()` has finalised the
|
|
726
|
+
* document's fonts.
|
|
727
|
+
*
|
|
728
|
+
* This matters because text measurement (anchor offset, line breaking) must
|
|
729
|
+
* use the *same* font that ultimately renders the glyphs. At draw time the
|
|
730
|
+
* font may still be unresolved (a non-WinAnsi run can trigger a build-time
|
|
731
|
+
* auto-embed of a system CIDFont), so measuring against the provisional
|
|
732
|
+
* Type1/Helvetica metrics would misplace centred/right-aligned text and
|
|
733
|
+
* break lines at the wrong points. Deferring keeps measurement and encoding
|
|
734
|
+
* consistent while preserving the fragment's draw-order slot (z-order).
|
|
735
|
+
*/
|
|
736
|
+
function emitTextBlock(stream, options, fontManager) {
|
|
737
|
+
stream.deferred(() => renderTextBlockLayout(options, fontManager));
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Lay out and render a text block from the font manager's *current*
|
|
741
|
+
* (build-time) state. Resolves the render resource name once and uses it for
|
|
742
|
+
* both measurement and encoding so the two never disagree.
|
|
743
|
+
*
|
|
744
|
+
* Layout is computed in the text's *local* coordinate frame — x grows along
|
|
745
|
+
* the baseline, y grows upward — then mapped to page space through the
|
|
746
|
+
* rotation matrix. This makes anchor alignment, multi-line word wrapping, and
|
|
747
|
+
* rotation compose correctly together: each line is offset by its anchor
|
|
748
|
+
* shift (along local x) and its line index (down local y), and a single
|
|
749
|
+
* rotation maps the whole block into place. Upright text (rotation 0) reduces
|
|
750
|
+
* to the identity mapping.
|
|
751
|
+
*/
|
|
752
|
+
function renderTextBlockLayout(options, fontManager) {
|
|
753
|
+
const { text, x, y, type1ResourceName, fontSize, anchor, maxWidth, lineHeightFactor, rotation } = options;
|
|
754
|
+
// Resolve the resource name once; measurement and rendering share it so a
|
|
755
|
+
// build-time auto-embedded CIDFont (or Type3 fallback) is measured with the
|
|
756
|
+
// metrics that will actually render the glyphs.
|
|
757
|
+
const measureResource = fontManager.resolveRenderResourceName(type1ResourceName);
|
|
758
|
+
const measure = (s) => fontManager.measureText(s, measureResource, fontSize);
|
|
759
|
+
const lines = maxWidth ? wrapTextLines(text, measure, maxWidth) : [text];
|
|
760
|
+
const leading = fontSize * lineHeightFactor;
|
|
761
|
+
// Rotation matrix [a b; c d] = [cos sin; -sin cos]; identity when upright.
|
|
762
|
+
const theta = (rotation * Math.PI) / 180;
|
|
763
|
+
const cos = rotation === 0 ? 1 : Math.cos(theta);
|
|
764
|
+
const sin = rotation === 0 ? 0 : Math.sin(theta);
|
|
765
|
+
const anchorFactor = anchor === "middle" ? 0.5 : anchor === "end" ? 1 : 0;
|
|
766
|
+
const parts = [];
|
|
767
|
+
for (let i = 0; i < lines.length; i++) {
|
|
768
|
+
// Local-frame origin of this line: anchor shift along x, line index down y.
|
|
769
|
+
const localX = anchorFactor === 0 ? 0 : -measure(lines[i]) * anchorFactor;
|
|
770
|
+
const localY = -i * leading;
|
|
771
|
+
// Map local origin into page space through the rotation matrix.
|
|
772
|
+
const tx = x + localX * cos + localY * -sin;
|
|
773
|
+
const ty = y + localX * sin + localY * cos;
|
|
774
|
+
parts.push(renderTextBlock(lines[i], cos, sin, -sin, cos, tx, ty, type1ResourceName, fontSize, fontManager));
|
|
775
|
+
}
|
|
776
|
+
return parts.join("\n");
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Produce the PDF operator string for a positioned text run, choosing the
|
|
780
|
+
* encoding from the font manager's *current* (build-time) state:
|
|
781
|
+
* - embedded font → single BT/ET with CIDFont hex encoding
|
|
782
|
+
* - Type3 fallback → split into WinAnsi (Type1) and per-glyph Type3 runs
|
|
783
|
+
* - neither → single BT/ET with Type1/WinAnsi encoding
|
|
784
|
+
*
|
|
785
|
+
* Must only be called after font resolution (i.e. from a deferred fragment).
|
|
704
786
|
*/
|
|
705
|
-
function
|
|
787
|
+
function renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager) {
|
|
788
|
+
const sink = new pdf_stream_1.PdfContentStream();
|
|
789
|
+
// Type3 splitting only applies when there is no embedded font but Type3
|
|
790
|
+
// fallback glyphs were generated. Otherwise the run renders as a single
|
|
791
|
+
// BT/ET pair, choosing the resource name from the now-settled state:
|
|
792
|
+
// if an embedded font exists (possibly auto-discovered at build time,
|
|
793
|
+
// after this run was drawn against a Type1 resource), the run must use it
|
|
794
|
+
// so `emitText` → `encodeText` produces CIDFont hex; without that switch
|
|
795
|
+
// the stale Type1 resource name would make `encodeText` return null and
|
|
796
|
+
// non-WinAnsi characters would degrade to spaces via the WinAnsi fallback.
|
|
797
|
+
const useType3 = fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont();
|
|
706
798
|
if (!useType3) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
799
|
+
const resourceName = fontManager.resolveRenderResourceName(type1ResourceName);
|
|
800
|
+
sink.beginText();
|
|
801
|
+
sink.setFont(resourceName, fontSize);
|
|
802
|
+
sink.setTextMatrix(a, b, c, d, tx, ty);
|
|
803
|
+
emitText(sink, fontManager, text, resourceName);
|
|
804
|
+
sink.endText();
|
|
805
|
+
return sink.toString();
|
|
713
806
|
}
|
|
714
807
|
// Type3 path: split into runs and advance origin along text direction
|
|
715
808
|
const runs = splitTextRuns(text, fontManager);
|
|
716
809
|
let curTx = tx;
|
|
717
810
|
let curTy = ty;
|
|
718
811
|
for (const run of runs) {
|
|
719
|
-
|
|
812
|
+
sink.beginText();
|
|
720
813
|
if (run.type3) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
814
|
+
sink.setFont(run.type3.resourceName, fontSize);
|
|
815
|
+
sink.setTextMatrix(a, b, c, d, curTx, curTy);
|
|
816
|
+
sink.showTextHex(run.type3.hex);
|
|
724
817
|
}
|
|
725
818
|
else {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
emitText(
|
|
819
|
+
sink.setFont(type1ResourceName, fontSize);
|
|
820
|
+
sink.setTextMatrix(a, b, c, d, curTx, curTy);
|
|
821
|
+
emitText(sink, fontManager, run.text, type1ResourceName);
|
|
729
822
|
}
|
|
730
|
-
|
|
823
|
+
sink.endText();
|
|
731
824
|
const w = fontManager.measureText(run.text, run.type3?.resourceName ?? type1ResourceName, fontSize);
|
|
732
825
|
// Advance along the text direction (first column of the matrix)
|
|
733
826
|
curTx += a * w;
|
|
734
827
|
curTy += b * w;
|
|
735
828
|
}
|
|
829
|
+
return sink.toString();
|
|
736
830
|
}
|
|
737
831
|
/**
|
|
738
832
|
* Split a line of text into runs of consecutive WinAnsi and non-WinAnsi
|
|
@@ -46,7 +46,7 @@ function updateFields(doc, options) {
|
|
|
46
46
|
// Build style → paragraphs index for STYLEREF
|
|
47
47
|
const styleIndex = buildStyleIndex(doc);
|
|
48
48
|
// Collect INDEX entries (XE fields) from all body content
|
|
49
|
-
const indexEntries = collectIndexEntries(doc);
|
|
49
|
+
const indexEntries = collectIndexEntries(doc, layout);
|
|
50
50
|
// Update body content
|
|
51
51
|
const newBody = updateBody(doc, layout, bookmarkInfo, seqValues, styleIndex, indexEntries, opts);
|
|
52
52
|
// If TOC was updated, register the TOC1..TOCn paragraph styles so the
|
|
@@ -614,23 +614,33 @@ function findStyleRef(styleIndex, styleName, bodyIndex) {
|
|
|
614
614
|
// =============================================================================
|
|
615
615
|
// Index Entry Collection (for INDEX field)
|
|
616
616
|
// =============================================================================
|
|
617
|
-
/**
|
|
618
|
-
|
|
617
|
+
/**
|
|
618
|
+
* Collect all XE (Index Entry) fields from the document body, tagging each
|
|
619
|
+
* with the page it falls on. Page numbers come from `layout.contentPages`
|
|
620
|
+
* keyed by the enclosing top-level body index (the same mechanism used for
|
|
621
|
+
* headings/TOC); entries inside nested structures inherit their outer block's
|
|
622
|
+
* page, falling back to 1 when layout produced no page for that block.
|
|
623
|
+
*/
|
|
624
|
+
function collectIndexEntries(doc, layout) {
|
|
619
625
|
const entries = [];
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
626
|
+
const { contentPages } = layout;
|
|
627
|
+
for (let i = 0; i < doc.body.length; i++) {
|
|
628
|
+
const page = contentPages[i] ?? 1;
|
|
629
|
+
(0, walker_1.walkBlocks)([doc.body[i]], {
|
|
630
|
+
visitRunContent(content) {
|
|
631
|
+
if (content.type !== "field") {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const { type, args } = parseFieldInstruction(content.instruction);
|
|
635
|
+
if (type === "XE") {
|
|
636
|
+
const term = parseXeTerm(args);
|
|
637
|
+
if (term) {
|
|
638
|
+
entries.push({ term, page });
|
|
639
|
+
}
|
|
630
640
|
}
|
|
631
641
|
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
634
644
|
return entries;
|
|
635
645
|
}
|
|
636
646
|
/** Parse the term from an XE field argument: XE "term" or XE term. */
|
|
@@ -647,17 +657,32 @@ function buildIndexContent(entries, args) {
|
|
|
647
657
|
if (entries.length === 0) {
|
|
648
658
|
return "";
|
|
649
659
|
}
|
|
650
|
-
//
|
|
651
|
-
|
|
660
|
+
// Merge entries that share the same term: a term marked on several pages
|
|
661
|
+
// produces a single index line listing each distinct page (Word behaviour),
|
|
662
|
+
// e.g. "widget, 1, 4" rather than two separate "widget" rows.
|
|
663
|
+
const byTerm = new Map();
|
|
664
|
+
for (const entry of entries) {
|
|
665
|
+
let pages = byTerm.get(entry.term);
|
|
666
|
+
if (!pages) {
|
|
667
|
+
pages = new Set();
|
|
668
|
+
byTerm.set(entry.term, pages);
|
|
669
|
+
}
|
|
670
|
+
pages.add(entry.page);
|
|
671
|
+
}
|
|
672
|
+
// Sort terms alphabetically; within each term sort pages numerically.
|
|
673
|
+
const merged = [...byTerm.entries()]
|
|
674
|
+
.map(([term, pages]) => ({ term, pages: [...pages].sort((a, b) => a - b) }))
|
|
675
|
+
.sort((a, b) => a.term.localeCompare(b.term));
|
|
676
|
+
const formatEntry = (e) => `${e.term}\t${e.pages.join(", ")}`;
|
|
652
677
|
// Check for \h switch (group by first letter with headings)
|
|
653
678
|
const grouped = /\\h\b/i.test(args);
|
|
654
679
|
if (!grouped) {
|
|
655
|
-
return
|
|
680
|
+
return merged.map(formatEntry).join("\n");
|
|
656
681
|
}
|
|
657
682
|
// Group by first letter
|
|
658
683
|
const lines = [];
|
|
659
684
|
let currentLetter = "";
|
|
660
|
-
for (const entry of
|
|
685
|
+
for (const entry of merged) {
|
|
661
686
|
const letter = entry.term.charAt(0).toUpperCase();
|
|
662
687
|
if (letter !== currentLetter) {
|
|
663
688
|
currentLetter = letter;
|
|
@@ -666,7 +691,7 @@ function buildIndexContent(entries, args) {
|
|
|
666
691
|
}
|
|
667
692
|
lines.push(currentLetter);
|
|
668
693
|
}
|
|
669
|
-
lines.push(
|
|
694
|
+
lines.push(formatEntry(entry));
|
|
670
695
|
}
|
|
671
696
|
return lines.join("\n");
|
|
672
697
|
}
|
|
@@ -5,17 +5,16 @@
|
|
|
5
5
|
* Provides types and utilities for working with Glossary Document parts,
|
|
6
6
|
* which contain AutoText entries, Quick Parts, and other Building Blocks.
|
|
7
7
|
*
|
|
8
|
-
* INTEGRATION STATUS: This module provides data
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* glossary
|
|
12
|
-
*
|
|
8
|
+
* INTEGRATION STATUS: This module provides the glossary data model and query
|
|
9
|
+
* helpers. To embed a glossary in a document, assign a {@link GlossaryDocument}
|
|
10
|
+
* to `doc.glossary`; the packager then serialises it to
|
|
11
|
+
* `word/glossary/document.xml`, registers the `glossaryDocument` relationship,
|
|
12
|
+
* and adds the `[Content_Types].xml` override (the canonical OOXML location
|
|
13
|
+
* Word reads Quick Parts / AutoText from). Glossary parts in existing files are
|
|
14
|
+
* round-tripped via the same channel. This module is useful for:
|
|
13
15
|
* - Building glossary data structures programmatically
|
|
14
16
|
* - Querying/filtering building block collections
|
|
15
|
-
* -
|
|
16
|
-
*
|
|
17
|
-
* To add glossary content to a document currently, include it as an
|
|
18
|
-
* OpaquePart with path "word/glossary/document.xml".
|
|
17
|
+
* - Assembling a glossary to attach via `doc.glossary`
|
|
19
18
|
*/
|
|
20
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
20
|
exports.createBuildingBlock = createBuildingBlock;
|
|
@@ -83,20 +83,98 @@ function convertNodeToMathML(node) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
function convertMathRunToMathML(node) {
|
|
86
|
-
const
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
const raw = node.text;
|
|
87
|
+
// A single OMML run can hold a mix of letters, digits, operators and
|
|
88
|
+
// whitespace (e.g. "a + b = "). MathML presentation markup expects each
|
|
89
|
+
// token to be wrapped in the element matching its semantic category:
|
|
90
|
+
// <mi> for identifiers, <mn> for numbers, <mo> for operators and <mtext>
|
|
91
|
+
// for runs of whitespace / other text. Tokenize the run and emit one
|
|
92
|
+
// element per token so a mixed run is no longer flattened into a single
|
|
93
|
+
// (incorrect) <mi>.
|
|
94
|
+
const normalVariant = node.properties?.italic === false;
|
|
95
|
+
const tokens = tokenizeMathRun(raw);
|
|
96
|
+
// Fast path / backwards-compatibility: a run that is a single token keeps
|
|
97
|
+
// producing exactly one element (e.g. "x" -> <mi>x</mi>, "42" -> <mn>42</mn>,
|
|
98
|
+
// "+" -> <mo>+</mo>), matching the historical output.
|
|
99
|
+
return tokens.map(tok => emitMathToken(tok, normalVariant)).join("");
|
|
100
|
+
}
|
|
101
|
+
function classifyMathChar(ch) {
|
|
102
|
+
if (/\s/.test(ch)) {
|
|
103
|
+
return "text";
|
|
104
|
+
}
|
|
105
|
+
if (isOperator(ch)) {
|
|
106
|
+
return "operator";
|
|
107
|
+
}
|
|
108
|
+
if (/[0-9.]/.test(ch)) {
|
|
109
|
+
return "number";
|
|
110
|
+
}
|
|
111
|
+
if (/[A-Za-z]/.test(ch)) {
|
|
112
|
+
return "identifier";
|
|
113
|
+
}
|
|
114
|
+
// Letters outside ASCII (Greek, CJK, etc.) and any other symbol fall back to
|
|
115
|
+
// identifier — this matches how OMML treats variable names.
|
|
116
|
+
return "identifier";
|
|
117
|
+
}
|
|
118
|
+
function tokenizeMathRun(text) {
|
|
119
|
+
const tokens = [];
|
|
120
|
+
// Use code points so astral / combined characters are not split mid-symbol.
|
|
121
|
+
const chars = Array.from(text);
|
|
122
|
+
for (const ch of chars) {
|
|
123
|
+
const kind = classifyMathChar(ch);
|
|
124
|
+
const last = tokens[tokens.length - 1];
|
|
125
|
+
// Operators are always emitted as their own token (each operator is a
|
|
126
|
+
// distinct <mo>). Numbers, identifiers and text coalesce with the
|
|
127
|
+
// previous token of the same kind so "42" stays a single <mn> and a run
|
|
128
|
+
// of spaces stays a single <mtext>.
|
|
129
|
+
if (last && last.kind === kind && kind !== "operator") {
|
|
130
|
+
last.value += ch;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
tokens.push({ kind, value: ch });
|
|
134
|
+
}
|
|
90
135
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
136
|
+
return mergeDecimalPoints(tokens);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A "." between two digit groups is a decimal point, not an operator. The
|
|
140
|
+
* char classifier sees "." as an operator (it is a valid math operator on its
|
|
141
|
+
* own, e.g. function composition), so stitch `<number> "." <number>` back into
|
|
142
|
+
* a single number token here.
|
|
143
|
+
*/
|
|
144
|
+
function mergeDecimalPoints(tokens) {
|
|
145
|
+
const out = [];
|
|
146
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
147
|
+
const tok = tokens[i];
|
|
148
|
+
const prev = out[out.length - 1];
|
|
149
|
+
const next = tokens[i + 1];
|
|
150
|
+
if (tok.kind === "operator" &&
|
|
151
|
+
tok.value === "." &&
|
|
152
|
+
prev &&
|
|
153
|
+
prev.kind === "number" &&
|
|
154
|
+
next &&
|
|
155
|
+
next.kind === "number") {
|
|
156
|
+
prev.value += "." + next.value;
|
|
157
|
+
i++; // consume the following number token
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
out.push(tok);
|
|
94
161
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
function emitMathToken(tok, normalVariant) {
|
|
165
|
+
const text = (0, encode_1.xmlEncode)(tok.value);
|
|
166
|
+
switch (tok.kind) {
|
|
167
|
+
case "operator":
|
|
168
|
+
return `<mo>${text}</mo>`;
|
|
169
|
+
case "number":
|
|
170
|
+
// A lone "." is not a number; treat it as an operator/text fallback.
|
|
171
|
+
return /\d/.test(tok.value) ? `<mn>${text}</mn>` : `<mo>${text}</mo>`;
|
|
172
|
+
case "text":
|
|
173
|
+
return `<mtext>${text}</mtext>`;
|
|
174
|
+
case "identifier":
|
|
175
|
+
default:
|
|
176
|
+
return normalVariant ? `<mi mathvariant="normal">${text}</mi>` : `<mi>${text}</mi>`;
|
|
98
177
|
}
|
|
99
|
-
return `<mi>${text}</mi>`;
|
|
100
178
|
}
|
|
101
179
|
function convertFractionToMathML(node) {
|
|
102
180
|
const num = childrenToMathML(node.numerator);
|
|
@@ -258,7 +336,11 @@ function convertMMLElement(el) {
|
|
|
258
336
|
}
|
|
259
337
|
case "msqrt": {
|
|
260
338
|
const content = convertMMLChildren(el.children);
|
|
261
|
-
|
|
339
|
+
// A bare square root has no degree. OOXML still emits an (empty)
|
|
340
|
+
// <m:deg/>, so we must set hideDegree → <m:degHide m:val="1"/>;
|
|
341
|
+
// otherwise Word treats the empty degree as visible and draws an empty
|
|
342
|
+
// degree box (a small square) at the radical's upper-left.
|
|
343
|
+
return { type: "mathRadical", content, hideDegree: true };
|
|
262
344
|
}
|
|
263
345
|
case "mroot": {
|
|
264
346
|
const children = getElementChildren(el);
|
|
@@ -19,6 +19,9 @@ exports.extractOleObjects = extractOleObjects;
|
|
|
19
19
|
exports.hasOleObjects = hasOleObjects;
|
|
20
20
|
exports.getOleObjectData = getOleObjectData;
|
|
21
21
|
exports.createOleEmbedding = createOleEmbedding;
|
|
22
|
+
exports.addOleObject = addOleObject;
|
|
23
|
+
const encode_1 = require("../../xml/encode.js");
|
|
24
|
+
const constants_1 = require("../constants");
|
|
22
25
|
const opc_paths_1 = require("../core/opc-paths");
|
|
23
26
|
// =============================================================================
|
|
24
27
|
// OLE Object Extraction
|
|
@@ -34,14 +37,33 @@ const opc_paths_1 = require("../core/opc-paths");
|
|
|
34
37
|
function extractOleObjects(doc) {
|
|
35
38
|
const objects = [];
|
|
36
39
|
const summary = {};
|
|
37
|
-
|
|
40
|
+
const pushObject = (obj) => {
|
|
41
|
+
objects.push(obj);
|
|
42
|
+
summary[obj.progId] = (summary[obj.progId] ?? 0) + 1;
|
|
43
|
+
};
|
|
44
|
+
// Structured OLE objects wired on document.xml.rels (preferred form —
|
|
45
|
+
// these carry the real rId and, when available, the round-tripped progId).
|
|
46
|
+
if (doc.oleObjects) {
|
|
47
|
+
for (const ole of doc.oleObjects) {
|
|
48
|
+
pushObject({
|
|
49
|
+
rId: ole.rId,
|
|
50
|
+
progId: ole.progId ?? detectProgIdFromData(ole.data),
|
|
51
|
+
objectType: "embedded",
|
|
52
|
+
displayAs: "icon",
|
|
53
|
+
imageRId: ole.previewRId,
|
|
54
|
+
fileName: (0, opc_paths_1.getFileName)(ole.path),
|
|
55
|
+
data: ole.data
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Scan opaque parts for OLE embeddings (legacy / hand-built documents that
|
|
60
|
+
// did not go through the structured oleObjects channel).
|
|
38
61
|
if (doc.opaqueParts) {
|
|
39
62
|
for (const part of doc.opaqueParts) {
|
|
40
63
|
if (isOleEmbedding(part.path)) {
|
|
41
64
|
const obj = parseOlePartMetadata(part);
|
|
42
65
|
if (obj) {
|
|
43
|
-
|
|
44
|
-
summary[obj.progId] = (summary[obj.progId] ?? 0) + 1;
|
|
66
|
+
pushObject(obj);
|
|
45
67
|
}
|
|
46
68
|
}
|
|
47
69
|
}
|
|
@@ -51,11 +73,10 @@ function extractOleObjects(doc) {
|
|
|
51
73
|
if (element.type === "opaqueDrawing") {
|
|
52
74
|
const oleFromDrawing = extractOleFromRawXml(element.rawXml);
|
|
53
75
|
if (oleFromDrawing) {
|
|
54
|
-
// Check if we already have this object
|
|
55
|
-
const exists = objects.some(o => o.rId === oleFromDrawing.rId);
|
|
76
|
+
// Check if we already have this object (by rId)
|
|
77
|
+
const exists = objects.some(o => o.rId === oleFromDrawing.rId && o.rId !== "");
|
|
56
78
|
if (!exists) {
|
|
57
|
-
|
|
58
|
-
summary[oleFromDrawing.progId] = (summary[oleFromDrawing.progId] ?? 0) + 1;
|
|
79
|
+
pushObject(oleFromDrawing);
|
|
59
80
|
}
|
|
60
81
|
}
|
|
61
82
|
}
|
|
@@ -66,6 +87,9 @@ function extractOleObjects(doc) {
|
|
|
66
87
|
* Check if a document contains any OLE embedded objects.
|
|
67
88
|
*/
|
|
68
89
|
function hasOleObjects(doc) {
|
|
90
|
+
if (doc.oleObjects && doc.oleObjects.length > 0) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
69
93
|
if (doc.opaqueParts) {
|
|
70
94
|
for (const part of doc.opaqueParts) {
|
|
71
95
|
if (isOleEmbedding(part.path)) {
|
|
@@ -80,6 +104,14 @@ function hasOleObjects(doc) {
|
|
|
80
104
|
* Returns undefined if not found.
|
|
81
105
|
*/
|
|
82
106
|
function getOleObjectData(doc, rId) {
|
|
107
|
+
// Structured OLE objects carry the exact rId used on document.xml.rels.
|
|
108
|
+
if (doc.oleObjects) {
|
|
109
|
+
for (const ole of doc.oleObjects) {
|
|
110
|
+
if (ole.rId === rId) {
|
|
111
|
+
return ole.data;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
83
115
|
if (!doc.opaqueParts) {
|
|
84
116
|
return undefined;
|
|
85
117
|
}
|
|
@@ -132,16 +164,14 @@ function createOleEmbedding(data, progId, options) {
|
|
|
132
164
|
const olePart = {
|
|
133
165
|
path: `word/embeddings/${fileName}`,
|
|
134
166
|
data,
|
|
135
|
-
contentType:
|
|
167
|
+
contentType: constants_1.ContentType.OleObject,
|
|
136
168
|
relationships: undefined
|
|
137
169
|
};
|
|
138
170
|
const oleRId = `rIdOle${oleSeq}`;
|
|
139
|
-
// progId is
|
|
140
|
-
//
|
|
141
|
-
// alongside without a separate channel. We don't need it here.
|
|
142
|
-
void progId;
|
|
171
|
+
// progId is carried back on the result so addOleObject() can persist it
|
|
172
|
+
// into the body `<o:OLEObject ProgID="…">` markup for round-trip.
|
|
143
173
|
if (!options?.previewImage) {
|
|
144
|
-
return { olePart, oleRId };
|
|
174
|
+
return { olePart, oleRId, progId };
|
|
145
175
|
}
|
|
146
176
|
if (!options.previewContentType) {
|
|
147
177
|
throw new Error("createOleEmbedding: options.previewImage requires options.previewContentType");
|
|
@@ -156,7 +186,78 @@ function createOleEmbedding(data, progId, options) {
|
|
|
156
186
|
relationships: undefined
|
|
157
187
|
};
|
|
158
188
|
const previewRId = `rIdOleImg${previewSeq}`;
|
|
159
|
-
return { olePart, oleRId, previewPart, previewRId };
|
|
189
|
+
return { olePart, oleRId, progId, previewPart, previewRId };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Wire an {@link OleEmbeddingResult} into a document so the OLE object is
|
|
193
|
+
* actually rendered and resolvable, returning a new {@link DocxDocument}.
|
|
194
|
+
*
|
|
195
|
+
* Unlike just stuffing the part into `opaqueParts` (which leaves the binary
|
|
196
|
+
* dangling — no relationship, no body reference), this:
|
|
197
|
+
*
|
|
198
|
+
* - registers the OLE binary (and optional preview) on
|
|
199
|
+
* `doc.oleObjects` so the packager emits a `word/_rels/document.xml.rels`
|
|
200
|
+
* relationship with the exact rId and a `[Content_Types].xml` override;
|
|
201
|
+
* - appends a body paragraph carrying a `<w:object>` / `<o:OLEObject>`
|
|
202
|
+
* that references the same rId and embeds the ProgId, so the object is
|
|
203
|
+
* visible in Word and round-trips through `readDocx`.
|
|
204
|
+
*
|
|
205
|
+
* @param doc - The document to add the OLE object to.
|
|
206
|
+
* @param embedding - Result from {@link createOleEmbedding}.
|
|
207
|
+
* @param options - Display geometry (defaults to a 2"×2" icon box).
|
|
208
|
+
*/
|
|
209
|
+
function addOleObject(doc, embedding, options) {
|
|
210
|
+
const widthPt = options?.widthPt ?? 96;
|
|
211
|
+
const heightPt = options?.heightPt ?? 96;
|
|
212
|
+
const drawAspect = (options?.displayAs ?? "icon") === "icon" ? "Icon" : "Content";
|
|
213
|
+
const olePartEntry = {
|
|
214
|
+
path: embedding.olePart.path,
|
|
215
|
+
data: embedding.olePart.data,
|
|
216
|
+
rId: embedding.oleRId,
|
|
217
|
+
progId: embedding.progId,
|
|
218
|
+
contentType: embedding.olePart.contentType,
|
|
219
|
+
...(embedding.previewPart && embedding.previewRId
|
|
220
|
+
? {
|
|
221
|
+
previewPath: embedding.previewPart.path,
|
|
222
|
+
previewData: embedding.previewPart.data,
|
|
223
|
+
previewRId: embedding.previewRId,
|
|
224
|
+
previewContentType: embedding.previewPart.contentType
|
|
225
|
+
}
|
|
226
|
+
: {})
|
|
227
|
+
};
|
|
228
|
+
// Build the VML-hosted <w:object>. The o:OLEObject carries ProgID + the
|
|
229
|
+
// r:id of the binary; the v:shape provides geometry and (when present) the
|
|
230
|
+
// preview image fill via v:imagedata. This is the canonical OOXML shape for
|
|
231
|
+
// an embedded OLE object (ECMA-376 §17.3.3.19 + VML).
|
|
232
|
+
const shapeId = `_ole_${embedding.oleRId}`;
|
|
233
|
+
const styleWidth = widthPt.toFixed(0);
|
|
234
|
+
const styleHeight = heightPt.toFixed(0);
|
|
235
|
+
const imageData = embedding.previewRId != null
|
|
236
|
+
? `<v:imagedata r:id="${(0, encode_1.xmlEncodeAttr)(embedding.previewRId)}" o:title=""/>`
|
|
237
|
+
: "";
|
|
238
|
+
const rawXml = `<w:object>` +
|
|
239
|
+
`<v:shape id="${(0, encode_1.xmlEncodeAttr)(shapeId)}" type="#_x0000_t75" ` +
|
|
240
|
+
`style="width:${styleWidth}pt;height:${styleHeight}pt">` +
|
|
241
|
+
imageData +
|
|
242
|
+
`</v:shape>` +
|
|
243
|
+
`<o:OLEObject Type="Embed" ProgID="${(0, encode_1.xmlEncodeAttr)(embedding.progId)}" ` +
|
|
244
|
+
`ShapeID="${(0, encode_1.xmlEncodeAttr)(shapeId)}" DrawAspect="${drawAspect}" ` +
|
|
245
|
+
`r:id="${(0, encode_1.xmlEncodeAttr)(embedding.oleRId)}"/>` +
|
|
246
|
+
`</w:object>`;
|
|
247
|
+
const referencedRIds = [embedding.oleRId];
|
|
248
|
+
if (embedding.previewRId != null) {
|
|
249
|
+
referencedRIds.push(embedding.previewRId);
|
|
250
|
+
}
|
|
251
|
+
const drawing = {
|
|
252
|
+
type: "opaqueDrawing",
|
|
253
|
+
rawXml,
|
|
254
|
+
referencedRIds
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
...doc,
|
|
258
|
+
body: [...doc.body, drawing],
|
|
259
|
+
oleObjects: [...(doc.oleObjects ?? []), olePartEntry]
|
|
260
|
+
};
|
|
160
261
|
}
|
|
161
262
|
/** Module-level counters used to allocate unique file names per call. */
|
|
162
263
|
let _oleSeq = 0;
|
|
@@ -248,17 +349,20 @@ function tryDecodeAscii(data) {
|
|
|
248
349
|
return str;
|
|
249
350
|
}
|
|
250
351
|
function extractOleFromRawXml(rawXml) {
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
352
|
+
// Pull metadata from the <o:OLEObject> element specifically, so a preview
|
|
353
|
+
// image's <v:imagedata r:id="…"> earlier in the markup is not mistaken for
|
|
354
|
+
// the OLE binary's relationship id.
|
|
355
|
+
const oleTag = rawXml.match(/<o:OLEObject\b[^>]*>/i)?.[0] ?? rawXml;
|
|
356
|
+
const progIdMatch = oleTag.match(/ProgID="([^"]+)"/i) ?? oleTag.match(/progId="([^"]+)"/i);
|
|
357
|
+
const rIdMatch = oleTag.match(/r:id="([^"]+)"/i);
|
|
358
|
+
const typeMatch = oleTag.match(/Type="([^"]+)"/i);
|
|
255
359
|
if (!progIdMatch) {
|
|
256
360
|
return null;
|
|
257
361
|
}
|
|
258
362
|
const progId = progIdMatch[1];
|
|
259
363
|
const rId = rIdMatch ? rIdMatch[1] : "";
|
|
260
364
|
const objectType = typeMatch && typeMatch[1].toLowerCase().includes("link") ? "linked" : "embedded";
|
|
261
|
-
// Extract dimensions
|
|
365
|
+
// Extract dimensions (from the surrounding shape, hence full rawXml)
|
|
262
366
|
const widthMatch = rawXml.match(/(?:cx|width)="(\d+)"/i);
|
|
263
367
|
const heightMatch = rawXml.match(/(?:cy|height)="(\d+)"/i);
|
|
264
368
|
return {
|