@cj-tech-master/excelts 9.6.0 → 9.6.1
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/browser/modules/archive/io/random-access.d.ts +1 -1
- package/dist/browser/modules/excel/workbook.browser.d.ts +1 -1
- package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.d.ts +3 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.js +30 -7
- package/dist/browser/modules/pdf/excel-bridge.d.ts +32 -0
- package/dist/browser/modules/pdf/excel-bridge.js +67 -1
- package/dist/browser/modules/pdf/word-bridge.d.ts +20 -15
- package/dist/browser/modules/pdf/word-bridge.js +49 -34
- package/dist/browser/modules/stream/common/consumers.d.ts +2 -1
- package/dist/browser/modules/word/advanced/diff.js +125 -13
- package/dist/browser/modules/word/advanced/drawing-shapes.js +3 -0
- package/dist/browser/modules/word/bridge/excel-bridge.js +21 -1
- package/dist/browser/modules/word/builder/document-handle.d.ts +2 -0
- package/dist/browser/modules/word/builder/document-handle.js +14 -2
- package/dist/browser/modules/word/builder/paragraph-builders.js +10 -1
- package/dist/browser/modules/word/builder/run-builders.d.ts +19 -2
- package/dist/browser/modules/word/builder/run-builders.js +2 -6
- package/dist/browser/modules/word/convert/odt/odt.js +6 -1
- package/dist/browser/modules/word/layout/layout-full.d.ts +12 -0
- package/dist/browser/modules/word/layout/layout-full.js +74 -9
- package/dist/browser/modules/word/layout/layout-model.d.ts +12 -0
- package/dist/browser/modules/word/query/merge.js +26 -10
- package/dist/browser/modules/word/query/split.js +68 -2
- package/dist/browser/modules/word/reader/docx-reader.js +23 -0
- package/dist/browser/modules/word/security/cfb-reader.d.ts +14 -3
- package/dist/browser/modules/word/security/cfb-reader.js +271 -153
- package/dist/browser/modules/word/security/document-protection.js +10 -4
- package/dist/browser/modules/word/security/encryption.js +194 -32
- package/dist/browser/modules/word/types.d.ts +17 -0
- package/dist/browser/modules/word/units.d.ts +10 -4
- package/dist/browser/modules/word/units.js +10 -4
- package/dist/browser/modules/word/writer/document-writer.js +28 -4
- package/dist/browser/modules/word/writer/docx-packager.js +45 -5
- package/dist/browser/modules/word/writer/image-writer.d.ts +1 -1
- package/dist/browser/modules/word/writer/image-writer.js +2 -2
- package/dist/browser/modules/word/writer/render-context.d.ts +15 -0
- package/dist/browser/modules/word/writer/run-writer.js +8 -4
- package/dist/browser/modules/word/writer/section-writer.js +46 -35
- package/dist/browser/modules/word/writer/streaming-writer.js +4 -0
- package/dist/browser/modules/word/writer/styles-writer.js +11 -0
- package/dist/browser/modules/word/writer/table-writer.js +6 -0
- package/dist/cjs/modules/excel/xlsx/xform/comment/comment-xform.js +30 -7
- package/dist/cjs/modules/pdf/excel-bridge.js +67 -0
- package/dist/cjs/modules/pdf/word-bridge.js +49 -34
- package/dist/cjs/modules/word/advanced/diff.js +125 -13
- package/dist/cjs/modules/word/advanced/drawing-shapes.js +3 -0
- package/dist/cjs/modules/word/bridge/excel-bridge.js +21 -1
- package/dist/cjs/modules/word/builder/document-handle.js +14 -2
- package/dist/cjs/modules/word/builder/paragraph-builders.js +10 -1
- package/dist/cjs/modules/word/builder/run-builders.js +2 -6
- package/dist/cjs/modules/word/convert/odt/odt.js +6 -1
- package/dist/cjs/modules/word/layout/layout-full.js +74 -9
- package/dist/cjs/modules/word/query/merge.js +26 -10
- package/dist/cjs/modules/word/query/split.js +68 -2
- package/dist/cjs/modules/word/reader/docx-reader.js +23 -0
- package/dist/cjs/modules/word/security/cfb-reader.js +271 -153
- package/dist/cjs/modules/word/security/document-protection.js +10 -4
- package/dist/cjs/modules/word/security/encryption.js +193 -31
- package/dist/cjs/modules/word/units.js +10 -4
- package/dist/cjs/modules/word/writer/document-writer.js +28 -4
- package/dist/cjs/modules/word/writer/docx-packager.js +45 -5
- package/dist/cjs/modules/word/writer/image-writer.js +2 -2
- package/dist/cjs/modules/word/writer/run-writer.js +8 -4
- package/dist/cjs/modules/word/writer/section-writer.js +46 -35
- package/dist/cjs/modules/word/writer/streaming-writer.js +4 -0
- package/dist/cjs/modules/word/writer/styles-writer.js +11 -0
- package/dist/cjs/modules/word/writer/table-writer.js +6 -0
- package/dist/esm/modules/excel/xlsx/xform/comment/comment-xform.js +30 -7
- package/dist/esm/modules/pdf/excel-bridge.js +67 -1
- package/dist/esm/modules/pdf/word-bridge.js +49 -34
- package/dist/esm/modules/word/advanced/diff.js +125 -13
- package/dist/esm/modules/word/advanced/drawing-shapes.js +3 -0
- package/dist/esm/modules/word/bridge/excel-bridge.js +21 -1
- package/dist/esm/modules/word/builder/document-handle.js +14 -2
- package/dist/esm/modules/word/builder/paragraph-builders.js +10 -1
- package/dist/esm/modules/word/builder/run-builders.js +2 -6
- package/dist/esm/modules/word/convert/odt/odt.js +6 -1
- package/dist/esm/modules/word/layout/layout-full.js +74 -9
- package/dist/esm/modules/word/query/merge.js +26 -10
- package/dist/esm/modules/word/query/split.js +68 -2
- package/dist/esm/modules/word/reader/docx-reader.js +23 -0
- package/dist/esm/modules/word/security/cfb-reader.js +271 -153
- package/dist/esm/modules/word/security/document-protection.js +10 -4
- package/dist/esm/modules/word/security/encryption.js +194 -32
- package/dist/esm/modules/word/units.js +10 -4
- package/dist/esm/modules/word/writer/document-writer.js +28 -4
- package/dist/esm/modules/word/writer/docx-packager.js +45 -5
- package/dist/esm/modules/word/writer/image-writer.js +2 -2
- package/dist/esm/modules/word/writer/run-writer.js +8 -4
- package/dist/esm/modules/word/writer/section-writer.js +46 -35
- package/dist/esm/modules/word/writer/streaming-writer.js +4 -0
- package/dist/esm/modules/word/writer/styles-writer.js +11 -0
- package/dist/esm/modules/word/writer/table-writer.js +6 -0
- package/dist/iife/excelts.iife.js +20 -8
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +2 -2
- package/dist/types/modules/archive/io/random-access.d.ts +1 -1
- package/dist/types/modules/excel/workbook.browser.d.ts +1 -1
- package/dist/types/modules/excel/xlsx/xform/comment/comment-xform.d.ts +3 -0
- package/dist/types/modules/pdf/excel-bridge.d.ts +32 -0
- package/dist/types/modules/pdf/word-bridge.d.ts +20 -15
- package/dist/types/modules/stream/common/consumers.d.ts +2 -1
- package/dist/types/modules/word/builder/document-handle.d.ts +2 -0
- package/dist/types/modules/word/builder/run-builders.d.ts +19 -2
- package/dist/types/modules/word/layout/layout-full.d.ts +12 -0
- package/dist/types/modules/word/layout/layout-model.d.ts +12 -0
- package/dist/types/modules/word/security/cfb-reader.d.ts +14 -3
- package/dist/types/modules/word/types.d.ts +17 -0
- package/dist/types/modules/word/units.d.ts +10 -4
- package/dist/types/modules/word/writer/image-writer.d.ts +1 -1
- package/dist/types/modules/word/writer/render-context.d.ts +15 -0
- package/package.json +2 -2
|
@@ -316,8 +316,8 @@ export function renderShading(xml, shd) {
|
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
318
|
/** Render an inline image (w:drawing > wp:inline). */
|
|
319
|
-
function renderInlineImage(xml, img, imageRemap) {
|
|
320
|
-
const drawingId = img.drawingId ?? 1;
|
|
319
|
+
function renderInlineImage(xml, img, imageRemap, nextDocPrId) {
|
|
320
|
+
const drawingId = nextDocPrId?.() ?? img.drawingId ?? 1;
|
|
321
321
|
const name = img.name ?? "Picture";
|
|
322
322
|
// Resolve the relationship id used in r:embed: prefer a packager-provided
|
|
323
323
|
// remap (used when the model rId clashed with an existing relationship in
|
|
@@ -481,7 +481,11 @@ function renderFfData(xml, ff) {
|
|
|
481
481
|
}
|
|
482
482
|
if (ff.entries) {
|
|
483
483
|
for (const entry of ff.entries) {
|
|
484
|
-
|
|
484
|
+
// Word rejects FORMDROPDOWN list entries with an empty value
|
|
485
|
+
// ("Word experienced an error trying to open the file"). Substitute a
|
|
486
|
+
// single space so an intended blank/placeholder item still renders and
|
|
487
|
+
// the entry indices (and `w:default`) stay aligned.
|
|
488
|
+
xml.leafNode("w:listEntry", { "w:val": entry === "" ? " " : entry });
|
|
485
489
|
}
|
|
486
490
|
}
|
|
487
491
|
xml.closeNode();
|
|
@@ -668,7 +672,7 @@ function renderRunContent(xml, content, helpers) {
|
|
|
668
672
|
}
|
|
669
673
|
return true;
|
|
670
674
|
}
|
|
671
|
-
renderInlineImage(xml, content, helpers?.imageRemap);
|
|
675
|
+
renderInlineImage(xml, content, helpers?.imageRemap, helpers?.nextDocPrId);
|
|
672
676
|
return true;
|
|
673
677
|
case "field":
|
|
674
678
|
// Fields create their own runs — must be rendered outside the current run
|
|
@@ -87,6 +87,16 @@ function renderPageBorders(xml, borders) {
|
|
|
87
87
|
/** Render w:sectPr element. */
|
|
88
88
|
export function renderSectionProperties(xml, sect, insidePropertyChange = false) {
|
|
89
89
|
xml.openNode("w:sectPr");
|
|
90
|
+
// NOTE: Child element ordering below is dictated by the OOXML schema
|
|
91
|
+
// (ISO/IEC 29500 — CT_SectPr / EG_SectPrContents). Microsoft Word rejects
|
|
92
|
+
// (or silently "repairs") documents whose sectPr children are out of order,
|
|
93
|
+
// so the sequence here MUST stay:
|
|
94
|
+
// headerReference*, footerReference*, footnotePr, endnotePr, type, pgSz,
|
|
95
|
+
// pgMar, paperSrc, pgBorders, lnNumType, pgNumType, cols, formProt,
|
|
96
|
+
// vAlign, noEndnote, titlePg, textDirection, bidi, rtlGutter, docGrid,
|
|
97
|
+
// printerSettings
|
|
98
|
+
// (headerReference/footerReference come from EG_HdrFtrReferences, which the
|
|
99
|
+
// schema places ahead of EG_SectPrContents.)
|
|
90
100
|
// Header references
|
|
91
101
|
if (sect.headers) {
|
|
92
102
|
for (const ref of sect.headers) {
|
|
@@ -99,6 +109,14 @@ export function renderSectionProperties(xml, sect, insidePropertyChange = false)
|
|
|
99
109
|
renderHeaderFooterRef(xml, "w:footerReference", ref);
|
|
100
110
|
}
|
|
101
111
|
}
|
|
112
|
+
// Footnote properties
|
|
113
|
+
if (sect.footnoteProperties) {
|
|
114
|
+
renderNoteProperties(xml, "w:footnotePr", sect.footnoteProperties);
|
|
115
|
+
}
|
|
116
|
+
// Endnote properties
|
|
117
|
+
if (sect.endnoteProperties) {
|
|
118
|
+
renderNoteProperties(xml, "w:endnotePr", sect.endnoteProperties);
|
|
119
|
+
}
|
|
102
120
|
// Section break type
|
|
103
121
|
if (sect.breakType) {
|
|
104
122
|
xml.leafNode("w:type", { "w:val": sect.breakType });
|
|
@@ -131,29 +149,7 @@ export function renderSectionProperties(xml, sect, insidePropertyChange = false)
|
|
|
131
149
|
if (sect.pageBorders) {
|
|
132
150
|
renderPageBorders(xml, sect.pageBorders);
|
|
133
151
|
}
|
|
134
|
-
//
|
|
135
|
-
if (sect.columns) {
|
|
136
|
-
renderColumns(xml, sect.columns);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
xml.leafNode("w:cols", { "w:space": String(DEFAULT_COLUMN_SPACE) });
|
|
140
|
-
}
|
|
141
|
-
// Title page (different first page header/footer)
|
|
142
|
-
if (sect.titlePage) {
|
|
143
|
-
xml.leafNode("w:titlePg");
|
|
144
|
-
}
|
|
145
|
-
// Page numbering
|
|
146
|
-
if (sect.pageNumbering) {
|
|
147
|
-
const attrs = {};
|
|
148
|
-
if (sect.pageNumbering.start !== undefined) {
|
|
149
|
-
attrs["w:start"] = String(sect.pageNumbering.start);
|
|
150
|
-
}
|
|
151
|
-
if (sect.pageNumbering.format) {
|
|
152
|
-
attrs["w:fmt"] = sect.pageNumbering.format;
|
|
153
|
-
}
|
|
154
|
-
xml.leafNode("w:pgNumType", attrs);
|
|
155
|
-
}
|
|
156
|
-
// Line numbers
|
|
152
|
+
// Line numbers (must precede pgNumType per schema)
|
|
157
153
|
if (sect.lineNumbers) {
|
|
158
154
|
const attrs = {};
|
|
159
155
|
if (sect.lineNumbers.countBy !== undefined) {
|
|
@@ -170,10 +166,37 @@ export function renderSectionProperties(xml, sect, insidePropertyChange = false)
|
|
|
170
166
|
}
|
|
171
167
|
xml.leafNode("w:lnNumType", attrs);
|
|
172
168
|
}
|
|
169
|
+
// Page numbering
|
|
170
|
+
if (sect.pageNumbering) {
|
|
171
|
+
const attrs = {};
|
|
172
|
+
if (sect.pageNumbering.start !== undefined) {
|
|
173
|
+
attrs["w:start"] = String(sect.pageNumbering.start);
|
|
174
|
+
}
|
|
175
|
+
if (sect.pageNumbering.format) {
|
|
176
|
+
attrs["w:fmt"] = sect.pageNumbering.format;
|
|
177
|
+
}
|
|
178
|
+
xml.leafNode("w:pgNumType", attrs);
|
|
179
|
+
}
|
|
180
|
+
// Columns
|
|
181
|
+
if (sect.columns) {
|
|
182
|
+
renderColumns(xml, sect.columns);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
xml.leafNode("w:cols", { "w:space": String(DEFAULT_COLUMN_SPACE) });
|
|
186
|
+
}
|
|
187
|
+
// Form protection
|
|
188
|
+
if (sect.formProtection) {
|
|
189
|
+
xml.leafNode("w:formProt", { "w:val": "1" });
|
|
190
|
+
}
|
|
173
191
|
// Vertical alignment
|
|
174
192
|
if (sect.verticalAlign) {
|
|
175
193
|
xml.leafNode("w:vAlign", { "w:val": sect.verticalAlign });
|
|
176
194
|
}
|
|
195
|
+
// Title page (different first page header/footer) — schema places this
|
|
196
|
+
// after vAlign/noEndnote and before textDirection.
|
|
197
|
+
if (sect.titlePage) {
|
|
198
|
+
xml.leafNode("w:titlePg");
|
|
199
|
+
}
|
|
177
200
|
// Text direction
|
|
178
201
|
if (sect.textDirection) {
|
|
179
202
|
xml.leafNode("w:textDirection", { "w:val": sect.textDirection });
|
|
@@ -186,18 +209,6 @@ export function renderSectionProperties(xml, sect, insidePropertyChange = false)
|
|
|
186
209
|
if (sect.rtlGutter) {
|
|
187
210
|
xml.leafNode("w:rtlGutter");
|
|
188
211
|
}
|
|
189
|
-
// Form protection
|
|
190
|
-
if (sect.formProtection) {
|
|
191
|
-
xml.leafNode("w:formProt", { "w:val": "1" });
|
|
192
|
-
}
|
|
193
|
-
// Footnote properties
|
|
194
|
-
if (sect.footnoteProperties) {
|
|
195
|
-
renderNoteProperties(xml, "w:footnotePr", sect.footnoteProperties);
|
|
196
|
-
}
|
|
197
|
-
// Endnote properties
|
|
198
|
-
if (sect.endnoteProperties) {
|
|
199
|
-
renderNoteProperties(xml, "w:endnotePr", sect.endnoteProperties);
|
|
200
|
-
}
|
|
201
212
|
// Document grid
|
|
202
213
|
if (sect.docGrid) {
|
|
203
214
|
const gridAttrs = {
|
|
@@ -900,6 +900,7 @@ export class StreamingDocxWriter {
|
|
|
900
900
|
addXmlFile(PartPath.Comments, xml => renderComments(xml, this._options.comments, {
|
|
901
901
|
imageRemap: this._renderCtx.imageRIdRemap,
|
|
902
902
|
hyperlinkRIds: this._renderCtx.hyperlinkRIds,
|
|
903
|
+
nextDocPrId: this._renderCtx.ids.nextDocPrId,
|
|
903
904
|
rawXmlPolicy: this._renderCtx.rawXmlPolicy
|
|
904
905
|
}));
|
|
905
906
|
// Also write commentsExtended if any have done/parentId
|
|
@@ -973,6 +974,7 @@ export class StreamingDocxWriter {
|
|
|
973
974
|
addXmlFile(headerPath, xml => renderHeader(xml, headerDef.content, {
|
|
974
975
|
imageRemap: this._renderCtx.imageRIdRemap,
|
|
975
976
|
hyperlinkRIds: this._renderCtx.hyperlinkRIds,
|
|
977
|
+
nextDocPrId: this._renderCtx.ids.nextDocPrId,
|
|
976
978
|
rawXmlPolicy: this._renderCtx.rawXmlPolicy
|
|
977
979
|
}));
|
|
978
980
|
if (getRelationshipCount(hRels) > 0) {
|
|
@@ -1043,6 +1045,7 @@ export class StreamingDocxWriter {
|
|
|
1043
1045
|
addXmlFile(footerPath, xml => renderFooter(xml, footerDef.content, {
|
|
1044
1046
|
imageRemap: this._renderCtx.imageRIdRemap,
|
|
1045
1047
|
hyperlinkRIds: this._renderCtx.hyperlinkRIds,
|
|
1048
|
+
nextDocPrId: this._renderCtx.ids.nextDocPrId,
|
|
1046
1049
|
rawXmlPolicy: this._renderCtx.rawXmlPolicy
|
|
1047
1050
|
}));
|
|
1048
1051
|
if (getRelationshipCount(fRels) > 0) {
|
|
@@ -1221,6 +1224,7 @@ export class StreamingDocxWriter {
|
|
|
1221
1224
|
notesHelpers: {
|
|
1222
1225
|
imageRemap: this._renderCtx.imageRIdRemap,
|
|
1223
1226
|
hyperlinkRIds: this._renderCtx.hyperlinkRIds,
|
|
1227
|
+
nextDocPrId: this._renderCtx.ids.nextDocPrId,
|
|
1224
1228
|
rawXmlPolicy: this._renderCtx.rawXmlPolicy
|
|
1225
1229
|
}
|
|
1226
1230
|
});
|
|
@@ -170,6 +170,17 @@ function renderStyle(xml, style) {
|
|
|
170
170
|
// For table styles
|
|
171
171
|
if (style.tableProperties) {
|
|
172
172
|
xml.openNode("w:tblPr");
|
|
173
|
+
// Per CT_TblPrBase order, band sizes precede w:tblW.
|
|
174
|
+
if (style.tableProperties.rowBandSize !== undefined) {
|
|
175
|
+
xml.leafNode("w:tblStyleRowBandSize", {
|
|
176
|
+
"w:val": String(style.tableProperties.rowBandSize)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (style.tableProperties.colBandSize !== undefined) {
|
|
180
|
+
xml.leafNode("w:tblStyleColBandSize", {
|
|
181
|
+
"w:val": String(style.tableProperties.colBandSize)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
173
184
|
if (style.tableProperties.width) {
|
|
174
185
|
xml.leafNode("w:tblW", {
|
|
175
186
|
"w:w": String(style.tableProperties.width.value),
|
|
@@ -168,6 +168,12 @@ function renderTableProperties(xml, tPr, insidePropertyChange = false) {
|
|
|
168
168
|
xml.leafNode("w:tblOverlap", { "w:val": tPr.float.overlap });
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
if (tPr.rowBandSize !== undefined) {
|
|
172
|
+
xml.leafNode("w:tblStyleRowBandSize", { "w:val": String(tPr.rowBandSize) });
|
|
173
|
+
}
|
|
174
|
+
if (tPr.colBandSize !== undefined) {
|
|
175
|
+
xml.leafNode("w:tblStyleColBandSize", { "w:val": String(tPr.colBandSize) });
|
|
176
|
+
}
|
|
171
177
|
if (tPr.width) {
|
|
172
178
|
renderTableWidth(xml, "w:tblW", tPr.width);
|
|
173
179
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CommentXform = void 0;
|
|
4
4
|
const base_xform_1 = require("../base-xform.js");
|
|
5
5
|
const rich_text_xform_1 = require("../strings/rich-text-xform.js");
|
|
6
|
+
const text_xform_1 = require("../strings/text-xform.js");
|
|
6
7
|
class CommentXform extends base_xform_1.BaseXform {
|
|
7
8
|
constructor(model) {
|
|
8
9
|
super();
|
|
@@ -17,6 +18,12 @@ class CommentXform extends base_xform_1.BaseXform {
|
|
|
17
18
|
}
|
|
18
19
|
return this._richTextXform;
|
|
19
20
|
}
|
|
21
|
+
get textXform() {
|
|
22
|
+
if (!this._textXform) {
|
|
23
|
+
this._textXform = new text_xform_1.TextXform();
|
|
24
|
+
}
|
|
25
|
+
return this._textXform;
|
|
26
|
+
}
|
|
20
27
|
render(xmlStream, model) {
|
|
21
28
|
const renderModel = model || this.model;
|
|
22
29
|
xmlStream.openNode("comment", {
|
|
@@ -52,6 +59,13 @@ class CommentXform extends base_xform_1.BaseXform {
|
|
|
52
59
|
this.parser = this.richTextXform;
|
|
53
60
|
this.parser.parseOpen(node);
|
|
54
61
|
return true;
|
|
62
|
+
case "t":
|
|
63
|
+
// Legacy comments (e.g. produced by openpyxl/LibreOffice) may store the
|
|
64
|
+
// body as a bare <t> directly under <text> with no <r> run wrapper.
|
|
65
|
+
// This is valid for the CT_Rst type, so treat it like a run without font.
|
|
66
|
+
this.parser = this.textXform;
|
|
67
|
+
this.parser.parseOpen(node);
|
|
68
|
+
return true;
|
|
55
69
|
default:
|
|
56
70
|
return false;
|
|
57
71
|
}
|
|
@@ -62,17 +76,26 @@ class CommentXform extends base_xform_1.BaseXform {
|
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
78
|
parseClose(name) {
|
|
79
|
+
if (this.parser) {
|
|
80
|
+
if (!this.parser.parseClose(name)) {
|
|
81
|
+
// The active sub-parser has finished. Collect its result.
|
|
82
|
+
if (this.parser === this._richTextXform) {
|
|
83
|
+
// <r> run: model is already a { font?, text } run.
|
|
84
|
+
this.model.note.texts.push(this.parser.model);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Bare <t> body (e.g. openpyxl/LibreOffice): wrap the plain string
|
|
88
|
+
// as a single run without font, mirroring a <r><t> run.
|
|
89
|
+
this.model.note.texts.push({ text: this.parser.model });
|
|
90
|
+
}
|
|
91
|
+
this.parser = undefined;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
65
95
|
switch (name) {
|
|
66
96
|
case "comment":
|
|
67
97
|
return false;
|
|
68
|
-
case "r":
|
|
69
|
-
this.model.note.texts.push(this.parser.model);
|
|
70
|
-
this.parser = undefined;
|
|
71
|
-
return true;
|
|
72
98
|
default:
|
|
73
|
-
if (this.parser) {
|
|
74
|
-
this.parser.parseClose(name);
|
|
75
|
-
}
|
|
76
99
|
return true;
|
|
77
100
|
}
|
|
78
101
|
}
|
|
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.excelToPdf = excelToPdf;
|
|
21
21
|
exports.chartToPdf = chartToPdf;
|
|
22
22
|
exports.createWordChartPdfRenderer = createWordChartPdfRenderer;
|
|
23
|
+
exports.createWordLayoutChartPdfRenderer = createWordLayoutChartPdfRenderer;
|
|
23
24
|
// Chart runtime is accessed through the chart-host-registry slot so
|
|
24
25
|
// `@pkg/pdf` can do excel-to-PDF conversion without statically pulling
|
|
25
26
|
// ~1.2 MB of chart rendering code into every consumer's bundle.
|
|
@@ -1177,3 +1178,69 @@ function createWordChartPdfRenderer() {
|
|
|
1177
1178
|
support.drawChartPdf(page, model, rect);
|
|
1178
1179
|
};
|
|
1179
1180
|
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Create a layout-aware chart renderer for use as the internal
|
|
1183
|
+
* `RenderLayoutOptions.chartRenderer` of the Word→PDF bridge.
|
|
1184
|
+
*
|
|
1185
|
+
* Unlike {@link createWordChartPdfRenderer} (which only sees the inner
|
|
1186
|
+
* classic `Chart` model), this renderer receives the full
|
|
1187
|
+
* {@link LayoutChart} and therefore handles **both** chart families
|
|
1188
|
+
* with the full Excel rendering engine:
|
|
1189
|
+
*
|
|
1190
|
+
* - Classic `<c:chart>` (`chartKind === "chart"`) → `wordChartToChartModel`
|
|
1191
|
+
* → `drawChartPdf` (vector).
|
|
1192
|
+
* - Modern `<cx:chartSpace>` ChartEx (`chartKind === "chartEx"`,
|
|
1193
|
+
* e.g. sunburst / treemap / waterfall / funnel / boxWhisker /
|
|
1194
|
+
* histogram / pareto / regionMap) → `parseChartEx` → `drawChartExPdf`
|
|
1195
|
+
* (vector) when the layout is vector-capable, otherwise the
|
|
1196
|
+
* pre-rendered SVG carried on the `LayoutChart` is left for the
|
|
1197
|
+
* translator's fallback.
|
|
1198
|
+
*
|
|
1199
|
+
* Returns `false` to decline a chart so the translator's built-in
|
|
1200
|
+
* fallback (inline SVG, then a titled placeholder box) takes over. This
|
|
1201
|
+
* keeps "fail soft" behaviour: a chart the engine can't draw still
|
|
1202
|
+
* renders *something* rather than a blank slot.
|
|
1203
|
+
*
|
|
1204
|
+
* Requires `installChartSupport()` to have been called.
|
|
1205
|
+
*/
|
|
1206
|
+
function createWordLayoutChartPdfRenderer() {
|
|
1207
|
+
return (layoutChart, page, rect) => {
|
|
1208
|
+
const support = (0, chart_host_registry_1.tryGetChartSupport)();
|
|
1209
|
+
if (!support) {
|
|
1210
|
+
// Chart support not installed — decline so the Word→PDF
|
|
1211
|
+
// translator falls back to the inline SVG / placeholder. This
|
|
1212
|
+
// mirrors the auto-detect contract in `word-bridge.ts`, where a
|
|
1213
|
+
// missing chart runtime must degrade gracefully rather than throw.
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1216
|
+
const source = layoutChart.source;
|
|
1217
|
+
if (layoutChart.chartKind === "chart") {
|
|
1218
|
+
// Classic chart: prefer the structured source, fall back to nothing.
|
|
1219
|
+
if (source && source.type === "chart") {
|
|
1220
|
+
support.drawChartPdf(page, (0, excel_bridge_1.wordChartToChartModel)(source.chart), rect);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
// ChartEx. Parse the carried `cx:chartSpace` XML into a ChartExModel
|
|
1226
|
+
// and render it as vector PDF when the layout IDs are supported.
|
|
1227
|
+
if (source && source.type === "chartEx" && source.chartExXml) {
|
|
1228
|
+
let model;
|
|
1229
|
+
try {
|
|
1230
|
+
model = support.parseChartEx(source.chartExXml);
|
|
1231
|
+
}
|
|
1232
|
+
catch {
|
|
1233
|
+
return false; // Malformed XML — let the fallback path handle it.
|
|
1234
|
+
}
|
|
1235
|
+
if (model && support.canRenderChartExAsVectorPdf(model)) {
|
|
1236
|
+
support.drawChartExPdf(page, model, rect, {
|
|
1237
|
+
title: layoutChart.title
|
|
1238
|
+
});
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
// Not vector-capable (or no source): decline so the translator
|
|
1243
|
+
// falls back to the inline SVG / placeholder.
|
|
1244
|
+
return false;
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
@@ -82,47 +82,59 @@ async function docxToPdf(doc, options) {
|
|
|
82
82
|
// caller explicitly overrode an axis. Margins are independent: the
|
|
83
83
|
// section's margins are applied unless the caller overrode them.
|
|
84
84
|
const layoutOptions = mapToLayoutOptions(doc, options);
|
|
85
|
-
// 2.
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Chart support not available — placeholder rendering takes over.
|
|
85
|
+
// 2. Try to obtain the built-in, layout-aware Excel chart renderer.
|
|
86
|
+
// It handles BOTH classic `<c:chart>` and modern `<cx:chartSpace>`
|
|
87
|
+
// ChartEx families (sunburst / treemap / waterfall / …). It is used
|
|
88
|
+
// directly when the caller supplies no `chartRenderer`, and as the
|
|
89
|
+
// fallback for ChartEx (which the public `Chart`-typed callback
|
|
90
|
+
// cannot express) or whenever a user callback declines a chart.
|
|
91
|
+
let builtInLayoutRenderer;
|
|
92
|
+
try {
|
|
93
|
+
const mod = await Promise.resolve().then(() => __importStar(require("./excel-bridge")));
|
|
94
|
+
if (typeof mod.createWordLayoutChartPdfRenderer === "function") {
|
|
95
|
+
builtInLayoutRenderer = mod.createWordLayoutChartPdfRenderer();
|
|
97
96
|
}
|
|
98
97
|
}
|
|
98
|
+
catch {
|
|
99
|
+
// Chart support not available — placeholder rendering takes over.
|
|
100
|
+
}
|
|
99
101
|
// 3. Run the layout engine. Everything from line wrapping to page
|
|
100
102
|
// breaks happens here; word-bridge no longer carries any of that.
|
|
101
103
|
const layout = (0, layout_full_1.layoutDocumentFull)(doc, layoutOptions);
|
|
102
104
|
// 4. Build a render-options object for the PDF translator. The
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
105
|
+
// chart-rendering precedence is:
|
|
106
|
+
// a. classic chart + user callback → user callback (its `false`
|
|
107
|
+
// return falls through to the built-in layout renderer);
|
|
108
|
+
// b. ChartEx, or classic chart with no user callback, or a
|
|
109
|
+
// declined user callback → built-in layout-aware renderer;
|
|
110
|
+
// c. neither available / both decline → translator fallback
|
|
111
|
+
// (inline SVG, then a titled placeholder box).
|
|
112
|
+
const userChartRenderer = options?.chartRenderer;
|
|
107
113
|
const renderOptions = {
|
|
108
114
|
title: doc.coreProperties?.title,
|
|
109
115
|
author: doc.coreProperties?.creator,
|
|
110
116
|
subject: doc.coreProperties?.subject,
|
|
111
117
|
defaultFont: options?.defaultFont ?? "Helvetica",
|
|
112
118
|
defaultFontSize: options?.defaultFontSize ?? 11,
|
|
113
|
-
chartRenderer:
|
|
119
|
+
chartRenderer: userChartRenderer || builtInLayoutRenderer
|
|
114
120
|
? (layoutChart, page, rect) => {
|
|
115
121
|
const src = layoutChart.source;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
// (a) Classic chart with a user-supplied callback: honour it
|
|
123
|
+
// first. Only fall through to the built-in renderer when
|
|
124
|
+
// it explicitly declines (`false`).
|
|
125
|
+
if (userChartRenderer && src && src.type === "chart") {
|
|
126
|
+
const handled = userChartRenderer(src.chart, page, rect);
|
|
127
|
+
if (handled !== false) {
|
|
128
|
+
return handled;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// (b) Built-in layout-aware renderer handles classic charts
|
|
132
|
+
// without a user callback AND all ChartEx charts.
|
|
133
|
+
if (builtInLayoutRenderer) {
|
|
134
|
+
return builtInLayoutRenderer(layoutChart, page, rect);
|
|
122
135
|
}
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
// placeholder runs instead of leaving a blank slot.
|
|
136
|
+
// (c) Decline so the translator's placeholder runs instead of
|
|
137
|
+
// leaving a blank slot.
|
|
126
138
|
return false;
|
|
127
139
|
}
|
|
128
140
|
: undefined
|
|
@@ -141,13 +153,11 @@ async function docxToPdf(doc, options) {
|
|
|
141
153
|
* portrait-oriented numbers); otherwise the layout engine's defaults
|
|
142
154
|
* (US Letter, 1-inch margins) take over.
|
|
143
155
|
*
|
|
144
|
-
* `headerMargin` / `footerMargin` are
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* rely on those parameters still get the section's `headerMargin` /
|
|
150
|
-
* `footerMargin` from sectionProperties when they exist.
|
|
156
|
+
* `headerMargin` / `footerMargin` are forwarded to the layout engine
|
|
157
|
+
* as header / footer band offsets (ECMA-376 `pgMar.header` /
|
|
158
|
+
* `pgMar.footer`). When omitted, the section's own header / footer
|
|
159
|
+
* margins apply; when neither exists the engine default of 36pt (0.5")
|
|
160
|
+
* is used.
|
|
151
161
|
*/
|
|
152
162
|
function mapToLayoutOptions(doc, options) {
|
|
153
163
|
const sectProps = doc.sectionProperties;
|
|
@@ -172,7 +182,12 @@ function mapToLayoutOptions(doc, options) {
|
|
|
172
182
|
marginTop: options?.marginTop ?? sectionMarginTopPt,
|
|
173
183
|
marginBottom: options?.marginBottom ?? sectionMarginBottomPt,
|
|
174
184
|
marginLeft: options?.marginLeft ?? sectionMarginLeftPt,
|
|
175
|
-
marginRight: options?.marginRight ?? sectionMarginRightPt
|
|
185
|
+
marginRight: options?.marginRight ?? sectionMarginRightPt,
|
|
186
|
+
// Header / footer offsets: only forward an explicit caller value.
|
|
187
|
+
// Leaving these undefined lets the layout engine fall back to the
|
|
188
|
+
// section's `pgMar.header` / `pgMar.footer` (then the 36pt default).
|
|
189
|
+
headerMargin: options?.headerMargin,
|
|
190
|
+
footerMargin: options?.footerMargin
|
|
176
191
|
};
|
|
177
192
|
const layoutOpts = {};
|
|
178
193
|
// Only attach pageGeometry when at least one axis is actually
|
|
@@ -147,24 +147,136 @@ function computeDiff(oldTexts, newTexts) {
|
|
|
147
147
|
}
|
|
148
148
|
// Reverse since we built it backwards
|
|
149
149
|
entries.reverse();
|
|
150
|
-
|
|
150
|
+
return pairModifications(entries);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Pair deletions with insertions that represent the *same* paragraph in a
|
|
154
|
+
* modified form, based on text similarity.
|
|
155
|
+
*
|
|
156
|
+
* The LCS pass only matches paragraphs whose text is byte-identical, so when
|
|
157
|
+
* every paragraph is lightly edited it produces all-deletions + all-insertions
|
|
158
|
+
* with no "modified" at all. Within each contiguous change block (a run of
|
|
159
|
+
* deletions/insertions bounded by unchanged entries), we greedily pair each
|
|
160
|
+
* deletion with the most similar insertion whose similarity clears
|
|
161
|
+
* {@link MODIFY_SIMILARITY_THRESHOLD}. Pairs become "modified"; anything left
|
|
162
|
+
* unpaired stays a pure deletion or insertion. This yields, e.g., a recipe
|
|
163
|
+
* whose steps were all tweaked → mostly "modified", with a removed step as a
|
|
164
|
+
* pure deletion and a brand-new step as a pure insertion.
|
|
165
|
+
*/
|
|
166
|
+
function pairModifications(entries) {
|
|
151
167
|
const result = [];
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
result.push(
|
|
168
|
+
let k = 0;
|
|
169
|
+
while (k < entries.length) {
|
|
170
|
+
const entry = entries[k];
|
|
171
|
+
if (entry.type !== "deleted" && entry.type !== "added") {
|
|
172
|
+
result.push(entry);
|
|
173
|
+
k++;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
// Collect the maximal contiguous run of deleted/added entries.
|
|
177
|
+
const dels = [];
|
|
178
|
+
const adds = [];
|
|
179
|
+
let j = k;
|
|
180
|
+
while (j < entries.length && (entries[j].type === "deleted" || entries[j].type === "added")) {
|
|
181
|
+
if (entries[j].type === "deleted") {
|
|
182
|
+
dels.push(entries[j]);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
adds.push(entries[j]);
|
|
186
|
+
}
|
|
187
|
+
j++;
|
|
188
|
+
}
|
|
189
|
+
result.push(...pairChangeBlock(dels, adds));
|
|
190
|
+
k = j;
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Pair one change block's deletions and insertions by similarity. Greedy:
|
|
196
|
+
* process deletions in order, each claiming the most similar still-unclaimed
|
|
197
|
+
* insertion above the threshold. Emits entries in old-index / new-index order.
|
|
198
|
+
*/
|
|
199
|
+
function pairChangeBlock(dels, adds) {
|
|
200
|
+
const usedAdd = new Array(adds.length).fill(false);
|
|
201
|
+
const out = [];
|
|
202
|
+
const leftoverAdds = [];
|
|
203
|
+
for (const del of dels) {
|
|
204
|
+
let bestIdx = -1;
|
|
205
|
+
let bestScore = MODIFY_SIMILARITY_THRESHOLD;
|
|
206
|
+
for (let a = 0; a < adds.length; a++) {
|
|
207
|
+
if (usedAdd[a]) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const score = textSimilarity(del.oldText ?? "", adds[a].newText ?? "");
|
|
211
|
+
if (score >= bestScore) {
|
|
212
|
+
bestScore = score;
|
|
213
|
+
bestIdx = a;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (bestIdx >= 0) {
|
|
217
|
+
usedAdd[bestIdx] = true;
|
|
218
|
+
out.push({
|
|
157
219
|
type: "modified",
|
|
158
|
-
oldIndex:
|
|
159
|
-
newIndex:
|
|
160
|
-
oldText:
|
|
161
|
-
newText:
|
|
220
|
+
oldIndex: del.oldIndex,
|
|
221
|
+
newIndex: adds[bestIdx].newIndex,
|
|
222
|
+
oldText: del.oldText,
|
|
223
|
+
newText: adds[bestIdx].newText
|
|
162
224
|
});
|
|
163
|
-
k++; // skip next
|
|
164
225
|
}
|
|
165
226
|
else {
|
|
166
|
-
|
|
227
|
+
out.push(del); // pure deletion
|
|
167
228
|
}
|
|
168
229
|
}
|
|
169
|
-
|
|
230
|
+
for (let a = 0; a < adds.length; a++) {
|
|
231
|
+
if (!usedAdd[a]) {
|
|
232
|
+
leftoverAdds.push(adds[a]); // pure insertion
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Order the block by position: modifications and deletions (old order)
|
|
236
|
+
// first, then the surviving pure insertions (new order). Both arrays are
|
|
237
|
+
// already in their natural index order from the LCS walk.
|
|
238
|
+
out.push(...leftoverAdds);
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
/** Minimum similarity (0..1) for a delete+add to be treated as a modification. */
|
|
242
|
+
const MODIFY_SIMILARITY_THRESHOLD = 0.5;
|
|
243
|
+
/**
|
|
244
|
+
* Similarity of two strings in [0, 1], combining word-set overlap (Jaccard)
|
|
245
|
+
* with a shared-prefix bonus. Cheap and dependency-free — good enough to tell
|
|
246
|
+
* "same paragraph, lightly edited" from "completely different paragraph".
|
|
247
|
+
*/
|
|
248
|
+
function textSimilarity(a, b) {
|
|
249
|
+
if (a === b) {
|
|
250
|
+
return 1;
|
|
251
|
+
}
|
|
252
|
+
if (a.length === 0 || b.length === 0) {
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
const wordsA = a.toLowerCase().split(/\s+/).filter(Boolean);
|
|
256
|
+
const wordsB = b.toLowerCase().split(/\s+/).filter(Boolean);
|
|
257
|
+
if (wordsA.length === 0 || wordsB.length === 0) {
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
const setA = new Set(wordsA);
|
|
261
|
+
const setB = new Set(wordsB);
|
|
262
|
+
let intersection = 0;
|
|
263
|
+
for (const w of setA) {
|
|
264
|
+
if (setB.has(w)) {
|
|
265
|
+
intersection++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const union = setA.size + setB.size - intersection;
|
|
269
|
+
const jaccard = union === 0 ? 0 : intersection / union;
|
|
270
|
+
// Shared-prefix bonus: paragraphs that begin the same ("Step 3: …") are very
|
|
271
|
+
// likely the same item edited, even if many words changed.
|
|
272
|
+
let prefix = 0;
|
|
273
|
+
const max = Math.min(a.length, b.length);
|
|
274
|
+
while (prefix < max && a[prefix] === b[prefix]) {
|
|
275
|
+
prefix++;
|
|
276
|
+
}
|
|
277
|
+
const prefixRatio = prefix / Math.max(a.length, b.length);
|
|
278
|
+
// Weight word overlap most, with shared prefix as a strong booster so
|
|
279
|
+
// "Hello World" → "Hello Earth" (half the words, same prefix) reads as a
|
|
280
|
+
// modification while unrelated text stays a delete+add.
|
|
281
|
+
return Math.min(1, jaccard * 0.7 + prefixRatio * 0.5);
|
|
170
282
|
}
|
|
@@ -73,6 +73,7 @@ function createShape(options) {
|
|
|
73
73
|
outlineWidth,
|
|
74
74
|
noOutline,
|
|
75
75
|
textContent: options.textBody?.paragraphs,
|
|
76
|
+
textBodyAnchor: options.textBody?.anchor,
|
|
76
77
|
altText: options.altText,
|
|
77
78
|
name: options.name,
|
|
78
79
|
horizontalPosition: options.horizontalPosition,
|
|
@@ -80,6 +81,8 @@ function createShape(options) {
|
|
|
80
81
|
wrap: options.wrap,
|
|
81
82
|
behindDoc: options.behindDoc,
|
|
82
83
|
rotation: options.rotation,
|
|
84
|
+
flipHorizontal: options.flipH,
|
|
85
|
+
flipVertical: options.flipV,
|
|
83
86
|
rawXml: rawXml.length > 0 ? rawXml : undefined,
|
|
84
87
|
_advancedFillXml: advanced.fillXml,
|
|
85
88
|
_advancedEffectsXml: advanced.effectsXml
|