@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
|
@@ -21,6 +21,7 @@ exports.getLineHeight = getLineHeight;
|
|
|
21
21
|
exports.isStandardFont = isStandardFont;
|
|
22
22
|
exports.getStandardFontNames = getStandardFontNames;
|
|
23
23
|
exports.mapToStandardFont = mapToStandardFont;
|
|
24
|
+
exports.styledFontVariant = styledFontVariant;
|
|
24
25
|
// =============================================================================
|
|
25
26
|
// Helvetica Metrics (afm data: Helvetica)
|
|
26
27
|
// =============================================================================
|
|
@@ -301,3 +302,46 @@ function mapToStandardFont(fontName) {
|
|
|
301
302
|
const lower = fontName.toLowerCase().trim();
|
|
302
303
|
return FONT_FAMILY_MAP[lower] ?? "Helvetica";
|
|
303
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Given a standard PDF base font and bold/italic flags, return the matching
|
|
307
|
+
* metric variant name (e.g. "Helvetica" + bold → "Helvetica-Bold"). This keeps
|
|
308
|
+
* width measurement consistent with the glyphs that are actually drawn, so
|
|
309
|
+
* bold/italic runs are measured with their true (wider) metrics rather than
|
|
310
|
+
* the regular ones. Falls back to the base name when a variant is unknown.
|
|
311
|
+
*/
|
|
312
|
+
function styledFontVariant(baseFont, bold, italic) {
|
|
313
|
+
const std = mapToStandardFont(baseFont);
|
|
314
|
+
if (!bold && !italic) {
|
|
315
|
+
return std;
|
|
316
|
+
}
|
|
317
|
+
// Determine the family from the resolved standard name.
|
|
318
|
+
const isTimes = std.startsWith("Times");
|
|
319
|
+
const isCourier = std.startsWith("Courier");
|
|
320
|
+
let candidate;
|
|
321
|
+
if (isTimes) {
|
|
322
|
+
// Times family uses -Roman / -Bold / -Italic / -BoldItalic.
|
|
323
|
+
if (bold && italic) {
|
|
324
|
+
candidate = "Times-BoldItalic";
|
|
325
|
+
}
|
|
326
|
+
else if (bold) {
|
|
327
|
+
candidate = "Times-Bold";
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
candidate = "Times-Italic";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// Helvetica / Courier families use -Bold / -Oblique / -BoldOblique.
|
|
335
|
+
const family = isCourier ? "Courier" : "Helvetica";
|
|
336
|
+
if (bold && italic) {
|
|
337
|
+
candidate = `${family}-BoldOblique`;
|
|
338
|
+
}
|
|
339
|
+
else if (bold) {
|
|
340
|
+
candidate = `${family}-Bold`;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
candidate = `${family}-Oblique`;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return candidate in FONT_DESCRIPTORS ? candidate : std;
|
|
347
|
+
}
|
|
@@ -121,6 +121,9 @@ function resolveOoxmlThemeColor(themeColorName, colors, tint, shade) {
|
|
|
121
121
|
// Helpers
|
|
122
122
|
// =============================================================================
|
|
123
123
|
function toHex2(n) {
|
|
124
|
-
|
|
124
|
+
// Emit uppercase hex to match the OOXML ST_HexColorRGB convention and the
|
|
125
|
+
// casing of theme scheme colors (which are stored uppercase), so a tinted /
|
|
126
|
+
// shaded result is consistent with an un-transformed passthrough.
|
|
127
|
+
const h = Math.max(0, Math.min(255, n)).toString(16).toUpperCase();
|
|
125
128
|
return h.length < 2 ? "0" + h : h;
|
|
126
129
|
}
|
|
@@ -36,7 +36,11 @@ class Note {
|
|
|
36
36
|
set model(value) {
|
|
37
37
|
const { note } = value;
|
|
38
38
|
const { texts } = note;
|
|
39
|
-
|
|
39
|
+
// A single, plain text run with no extra box geometry can be flattened
|
|
40
|
+
// back to a simple string. Custom width/height must keep the full config
|
|
41
|
+
// so the sizing survives the model round-trip.
|
|
42
|
+
const hasCustomSize = note.width !== undefined || note.height !== undefined;
|
|
43
|
+
if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1 && !hasCustomSize) {
|
|
40
44
|
this.note = texts[0].text;
|
|
41
45
|
}
|
|
42
46
|
else {
|
|
@@ -213,12 +213,13 @@ class Row {
|
|
|
213
213
|
// (e.g. DB entities). Cell.value setter handles unknown values via Value.getType
|
|
214
214
|
// fallback to JSON type, so this cast is safe at runtime.
|
|
215
215
|
this._worksheet.eachColumnKey((column, key) => {
|
|
216
|
-
|
|
216
|
+
const resolved = resolveColumnKeyValue(value, key);
|
|
217
|
+
if (resolved !== undefined) {
|
|
217
218
|
this.getCellEx({
|
|
218
219
|
address: colCache.encodeAddress(this._number, column.number),
|
|
219
220
|
row: this._number,
|
|
220
221
|
col: column.number
|
|
221
|
-
}).value =
|
|
222
|
+
}).value = resolved;
|
|
222
223
|
}
|
|
223
224
|
});
|
|
224
225
|
}
|
|
@@ -459,4 +460,36 @@ class Row {
|
|
|
459
460
|
this.style = value.style ? structuredClone(value.style) : {};
|
|
460
461
|
}
|
|
461
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Resolve a column key against a row object, supporting dotted nested paths.
|
|
465
|
+
*
|
|
466
|
+
* A key without a `.` takes the original fast path (`obj[key]`), preserving
|
|
467
|
+
* exact backward compatibility — including keys that legitimately contain a
|
|
468
|
+
* dot only as a flat property name, which still resolve via the fast path
|
|
469
|
+
* first. A dotted key (e.g. `"address.city"`) is resolved by walking each
|
|
470
|
+
* segment; if any segment is missing or not an object, the result is
|
|
471
|
+
* `undefined` (the same signal the caller already uses to skip a cell).
|
|
472
|
+
*
|
|
473
|
+
* @param obj - The row object supplied to `row.values = {...}` / `addRow({...})`.
|
|
474
|
+
* @param key - The column key, optionally a dotted path.
|
|
475
|
+
* @returns The resolved value, or `undefined` when the path cannot be followed.
|
|
476
|
+
*/
|
|
477
|
+
function resolveColumnKeyValue(obj, key) {
|
|
478
|
+
// Fast path: a flat key (no dot) or an exact flat property match. This keeps
|
|
479
|
+
// existing behaviour identical and also lets a literal "a.b" property win
|
|
480
|
+
// over nested traversal when it is actually present on the object.
|
|
481
|
+
const direct = obj[key];
|
|
482
|
+
if (direct !== undefined || !key.includes(".")) {
|
|
483
|
+
return direct;
|
|
484
|
+
}
|
|
485
|
+
// Dotted path: walk segments, bailing out to undefined on any gap.
|
|
486
|
+
let current = obj;
|
|
487
|
+
for (const segment of key.split(".")) {
|
|
488
|
+
if (current === null || typeof current !== "object") {
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
current = current[segment];
|
|
492
|
+
}
|
|
493
|
+
return current;
|
|
494
|
+
}
|
|
462
495
|
export { Row };
|
|
@@ -387,13 +387,33 @@ export class WorkbookWriterBase {
|
|
|
387
387
|
* ```
|
|
388
388
|
*/
|
|
389
389
|
addImage(image) {
|
|
390
|
+
const { svg, ...raster } = image;
|
|
391
|
+
if (svg &&
|
|
392
|
+
raster.link &&
|
|
393
|
+
raster.buffer == null &&
|
|
394
|
+
raster.base64 == null &&
|
|
395
|
+
raster.filename == null) {
|
|
396
|
+
throw new ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
|
|
397
|
+
}
|
|
390
398
|
const id = this.media.length;
|
|
391
399
|
const medium = {
|
|
392
|
-
...
|
|
400
|
+
...raster,
|
|
393
401
|
type: "image",
|
|
394
|
-
name: `image${id}.${
|
|
402
|
+
name: `image${id}.${raster.extension}`
|
|
395
403
|
};
|
|
396
404
|
this.media.push(medium);
|
|
405
|
+
if (svg) {
|
|
406
|
+
// Register the SVG companion as a second image medium and link it back to
|
|
407
|
+
// the raster blip so the drawing serializer emits the svgBlip extension.
|
|
408
|
+
const svgId = this.media.length;
|
|
409
|
+
this.media.push({
|
|
410
|
+
...svg,
|
|
411
|
+
type: "image",
|
|
412
|
+
extension: "svg",
|
|
413
|
+
name: `image${svgId}.svg`
|
|
414
|
+
});
|
|
415
|
+
medium.svgMediaId = svgId;
|
|
416
|
+
}
|
|
397
417
|
return id;
|
|
398
418
|
}
|
|
399
419
|
getImage(id) {
|
|
@@ -122,6 +122,23 @@ export function buildDrawingAnchorsAndRels(media, existingRels, options) {
|
|
|
122
122
|
},
|
|
123
123
|
range: medium.range
|
|
124
124
|
};
|
|
125
|
+
// SVG companion: allocate (and dedupe) a rel for the vector media, then
|
|
126
|
+
// record its rId so the blip serializer emits the asvg:svgBlip extension.
|
|
127
|
+
if (bookImage.svgMediaId !== undefined) {
|
|
128
|
+
const svgKey = `svg:${bookImage.svgMediaId}`;
|
|
129
|
+
let rIdSvg = imageRIdMap[svgKey];
|
|
130
|
+
if (!rIdSvg) {
|
|
131
|
+
const svgImage = options.getBookImage(bookImage.svgMediaId);
|
|
132
|
+
if (svgImage) {
|
|
133
|
+
rIdSvg = options.nextRId(rels);
|
|
134
|
+
imageRIdMap[svgKey] = rIdSvg;
|
|
135
|
+
rels.push(buildImageRel(rIdSvg, svgImage));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (rIdSvg) {
|
|
139
|
+
anchor.picture.svgRId = rIdSvg;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
125
142
|
// Pass through watermark opacity as alphaModFix
|
|
126
143
|
if (medium.opacity !== undefined) {
|
|
127
144
|
const clamped = Math.max(0, Math.min(1, medium.opacity));
|
|
@@ -172,8 +189,8 @@ export function filterDrawingAnchors(anchors) {
|
|
|
172
189
|
if (a.range?.br && a.shape) {
|
|
173
190
|
return true;
|
|
174
191
|
}
|
|
175
|
-
// One-cell anchors need a valid picture
|
|
176
|
-
if (!a.range?.br && !a.picture && !a.graphicFrame) {
|
|
192
|
+
// One-cell anchors need a valid picture, graphicFrame (charts) or shape.
|
|
193
|
+
if (!a.range?.br && !a.picture && !a.graphicFrame && !a.shape) {
|
|
177
194
|
return false;
|
|
178
195
|
}
|
|
179
196
|
// Two-cell anchors need either picture, shape, or graphicFrame (charts)
|
|
@@ -20,7 +20,7 @@ import { parseNumberFromCsv } from "../csv/utils/number.js";
|
|
|
20
20
|
import { getChartSupport } from "./chart-host-registry.js";
|
|
21
21
|
import { Chartsheet } from "./chartsheet.js";
|
|
22
22
|
import { DefinedNames } from "./defined-names.js";
|
|
23
|
-
import { ExcelDownloadError, ExcelNotSupportedError, WorksheetNameError } from "./errors.js";
|
|
23
|
+
import { ExcelDownloadError, ExcelNotSupportedError, ImageError, WorksheetNameError } from "./errors.js";
|
|
24
24
|
import { withPivotChartSource } from "./pivot-chart.js";
|
|
25
25
|
import { WorkbookReader } from "./stream/workbook-reader.browser.js";
|
|
26
26
|
import { WorkbookWriter } from "./stream/workbook-writer.browser.js";
|
|
@@ -1733,10 +1733,40 @@ class Workbook {
|
|
|
1733
1733
|
* const id = workbook.addImage({ extension: "png", link: "https://example.com/logo.png" });
|
|
1734
1734
|
* worksheet.addImage(id, "B2:D6");
|
|
1735
1735
|
* ```
|
|
1736
|
+
*
|
|
1737
|
+
* @example SVG with raster fallback — crisp in modern Excel, safe everywhere
|
|
1738
|
+
* ```typescript
|
|
1739
|
+
* const id = workbook.addImage({
|
|
1740
|
+
* buffer: pngFallbackBytes, // shown by older Excel / non-SVG consumers
|
|
1741
|
+
* extension: "png",
|
|
1742
|
+
* svg: { buffer: svgBytes } // shown by Excel 2016+
|
|
1743
|
+
* });
|
|
1744
|
+
* worksheet.addImage(id, "B2:D6");
|
|
1745
|
+
* ```
|
|
1736
1746
|
*/
|
|
1737
1747
|
addImage(image) {
|
|
1748
|
+
const { svg, ...raster } = image;
|
|
1749
|
+
if (svg &&
|
|
1750
|
+
raster.link &&
|
|
1751
|
+
raster.buffer == null &&
|
|
1752
|
+
raster.base64 == null &&
|
|
1753
|
+
raster.filename == null) {
|
|
1754
|
+
// An SVG companion needs an embedded raster fallback; a *linked* (external)
|
|
1755
|
+
// raster has no package part to attach the svgBlip extension to.
|
|
1756
|
+
throw new ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
|
|
1757
|
+
}
|
|
1738
1758
|
const id = this.media.length;
|
|
1739
|
-
|
|
1759
|
+
const rasterMedia = { ...raster, type: "image" };
|
|
1760
|
+
this.media.push(rasterMedia);
|
|
1761
|
+
if (svg) {
|
|
1762
|
+
// Register the SVG as a second `type: "image"` media so it flows through
|
|
1763
|
+
// the existing media naming, content-types, and zip-writing paths. Link
|
|
1764
|
+
// it back to the raster blip so the drawing serializer can emit the
|
|
1765
|
+
// asvg:svgBlip extension.
|
|
1766
|
+
const svgId = this.media.length;
|
|
1767
|
+
this.media.push({ ...svg, type: "image", extension: "svg" });
|
|
1768
|
+
rasterMedia.svgMediaId = svgId;
|
|
1769
|
+
}
|
|
1740
1770
|
return id;
|
|
1741
1771
|
}
|
|
1742
1772
|
getImage(id) {
|
|
@@ -105,6 +105,8 @@ class Worksheet {
|
|
|
105
105
|
this.autoFilter = options.autoFilter ?? null;
|
|
106
106
|
// for images, etc
|
|
107
107
|
this._media = [];
|
|
108
|
+
// for user-drawn shapes (rectangles, lines, text boxes, …)
|
|
109
|
+
this._shapes = [];
|
|
108
110
|
// for charts
|
|
109
111
|
this._charts = [];
|
|
110
112
|
this._sparklineGroups = [];
|
|
@@ -1009,6 +1011,85 @@ class Worksheet {
|
|
|
1009
1011
|
};
|
|
1010
1012
|
this._media.push(new Image(this, model));
|
|
1011
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Add a free-form drawing shape (rectangle, ellipse, line, text box, …) to
|
|
1016
|
+
* the worksheet, anchored to a cell range.
|
|
1017
|
+
*
|
|
1018
|
+
* Unlike images, shapes need no media file — the geometry, fill, outline and
|
|
1019
|
+
* optional text label are written directly into the drawing part.
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* ```typescript
|
|
1023
|
+
* worksheet.addShape({
|
|
1024
|
+
* type: "rect",
|
|
1025
|
+
* range: "B2:D5",
|
|
1026
|
+
* fillColor: "FFD966",
|
|
1027
|
+
* lineColor: "000000",
|
|
1028
|
+
* lineWidth: 1,
|
|
1029
|
+
* text: "Important"
|
|
1030
|
+
* });
|
|
1031
|
+
* ```
|
|
1032
|
+
*/
|
|
1033
|
+
addShape(options) {
|
|
1034
|
+
const range = options.range;
|
|
1035
|
+
// A shape must cover an area, mirroring images. Reject inputs that resolve
|
|
1036
|
+
// to no size up front, with a clear shape-specific message — otherwise the
|
|
1037
|
+
// failure surfaces much later as a confusing `ImageError` from the internal
|
|
1038
|
+
// range parser when the worksheet is serialized.
|
|
1039
|
+
const hasArea = (typeof range === "string" && range.includes(":")) ||
|
|
1040
|
+
(typeof range === "object" &&
|
|
1041
|
+
range !== null &&
|
|
1042
|
+
("br" in range || "ext" in range || "pos" in range));
|
|
1043
|
+
if (!hasArea) {
|
|
1044
|
+
throw new ImageError('addShape requires a range covering an area: a cell range like "B2:D5", or an object with `br`, `ext`, or `pos`.');
|
|
1045
|
+
}
|
|
1046
|
+
this._shapes.push({
|
|
1047
|
+
type: "shape",
|
|
1048
|
+
shapeType: options.type ?? "rect",
|
|
1049
|
+
range,
|
|
1050
|
+
fillColor: options.fillColor,
|
|
1051
|
+
lineColor: options.lineColor,
|
|
1052
|
+
lineWidth: options.lineWidth,
|
|
1053
|
+
text: options.text,
|
|
1054
|
+
name: options.name
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
/** All shapes added to this worksheet. */
|
|
1058
|
+
getShapes() {
|
|
1059
|
+
return this._shapes.slice();
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Resolve a shape's `range` into concrete two-cell anchor coordinates,
|
|
1063
|
+
* reusing the `Image` range parser so cell-address/anchor handling stays in
|
|
1064
|
+
* one place. Returns a serializable ShapeModel for the worksheet xform.
|
|
1065
|
+
*/
|
|
1066
|
+
_resolveShapeModel(shape) {
|
|
1067
|
+
let range;
|
|
1068
|
+
try {
|
|
1069
|
+
const probe = new Image(this, { type: "image", imageId: "", range: shape.range });
|
|
1070
|
+
// The probe is always an "image" type, so its model carries `range`.
|
|
1071
|
+
range = probe.model.range;
|
|
1072
|
+
}
|
|
1073
|
+
catch {
|
|
1074
|
+
// Range could not be parsed into an anchor (addShape validates the common
|
|
1075
|
+
// cases up front; this guards exotic inputs). Drop the anchor so the
|
|
1076
|
+
// serializer skips this shape rather than failing the whole worksheet.
|
|
1077
|
+
range = undefined;
|
|
1078
|
+
}
|
|
1079
|
+
if (!range) {
|
|
1080
|
+
return { ...shape, anchorRange: undefined };
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
...shape,
|
|
1084
|
+
anchorRange: {
|
|
1085
|
+
tl: range.tl,
|
|
1086
|
+
br: range.br,
|
|
1087
|
+
ext: range.ext,
|
|
1088
|
+
pos: range.pos,
|
|
1089
|
+
editAs: range.editAs
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1012
1093
|
getImages() {
|
|
1013
1094
|
return this._media.filter(m => m.type === "image");
|
|
1014
1095
|
}
|
|
@@ -1754,6 +1835,7 @@ class Worksheet {
|
|
|
1754
1835
|
views: this.views,
|
|
1755
1836
|
autoFilter: this.autoFilter,
|
|
1756
1837
|
media: this._media.map(medium => medium.model),
|
|
1838
|
+
shapes: this._shapes.map(shape => this._resolveShapeModel(shape)),
|
|
1757
1839
|
sheetProtection: this.sheetProtection,
|
|
1758
1840
|
tables: Object.values(this.tables).map(table => table.model),
|
|
1759
1841
|
pivotTables: this.pivotTables,
|
|
@@ -1819,6 +1901,7 @@ class Worksheet {
|
|
|
1819
1901
|
this.views = value.views;
|
|
1820
1902
|
this.autoFilter = value.autoFilter;
|
|
1821
1903
|
this._media = value.media.map(medium => new Image(this, medium));
|
|
1904
|
+
this._shapes = value.shapes ? value.shapes.slice() : [];
|
|
1822
1905
|
// Restore watermark state from media entries
|
|
1823
1906
|
this._watermark = value.watermark ?? null;
|
|
1824
1907
|
if (!this._watermark) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { BaseXform } from "../base-xform.js";
|
|
2
2
|
import { VmlClientDataXform } from "./vml-client-data-xform.js";
|
|
3
3
|
import { VmlTextboxXform } from "./vml-textbox-xform.js";
|
|
4
|
+
/** Default comment box geometry in points (matches legacy Excel notes). */
|
|
5
|
+
const DEFAULT_NOTE_WIDTH_PT = 97.8;
|
|
6
|
+
const DEFAULT_NOTE_HEIGHT_PT = 59.1;
|
|
4
7
|
class VmlShapeXform extends BaseXform {
|
|
5
8
|
constructor() {
|
|
6
9
|
super();
|
|
@@ -37,6 +40,21 @@ class VmlShapeXform extends BaseXform {
|
|
|
37
40
|
editAs: "",
|
|
38
41
|
protection: {}
|
|
39
42
|
};
|
|
43
|
+
{
|
|
44
|
+
// Recover the comment box geometry from the VML style string
|
|
45
|
+
// (e.g. "...width:120pt;height:80pt;..."). Only surface width/height
|
|
46
|
+
// when they differ from the legacy defaults, so untouched notes keep
|
|
47
|
+
// a clean model (and stay byte-compatible with prior behaviour).
|
|
48
|
+
const style = node.attributes.style ?? "";
|
|
49
|
+
const width = VmlShapeXform.parseStyleLength(style, "width");
|
|
50
|
+
const height = VmlShapeXform.parseStyleLength(style, "height");
|
|
51
|
+
if (width !== undefined && width !== DEFAULT_NOTE_WIDTH_PT) {
|
|
52
|
+
this.model.width = width;
|
|
53
|
+
}
|
|
54
|
+
if (height !== undefined && height !== DEFAULT_NOTE_HEIGHT_PT) {
|
|
55
|
+
this.model.height = height;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
40
58
|
break;
|
|
41
59
|
default:
|
|
42
60
|
this.parser = this.map[node.name];
|
|
@@ -75,13 +93,29 @@ class VmlShapeXform extends BaseXform {
|
|
|
75
93
|
return true;
|
|
76
94
|
}
|
|
77
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Extract a points-valued length (e.g. `width:120pt`) from a VML style
|
|
98
|
+
* string. Returns `undefined` when the property is absent or not in `pt`.
|
|
99
|
+
*/
|
|
100
|
+
static parseStyleLength(style, prop) {
|
|
101
|
+
const match = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([0-9.]+)pt`, "i").exec(style);
|
|
102
|
+
if (!match) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const value = parseFloat(match[1]);
|
|
106
|
+
return Number.isFinite(value) ? value : undefined;
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
|
-
VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) => {
|
|
110
|
+
const width = model.note?.width ?? DEFAULT_NOTE_WIDTH_PT;
|
|
111
|
+
const height = model.note?.height ?? DEFAULT_NOTE_HEIGHT_PT;
|
|
112
|
+
return {
|
|
113
|
+
id: `_x0000_s${1025 + index}`,
|
|
114
|
+
type: "#_x0000_t202",
|
|
115
|
+
style: `position:absolute; margin-left:105.3pt;margin-top:10.5pt;width:${width}pt;height:${height}pt;z-index:1;visibility:hidden`,
|
|
116
|
+
fillcolor: "infoBackground [80]",
|
|
117
|
+
strokecolor: "none [81]",
|
|
118
|
+
"o:insetmode": model.note.margins && model.note.margins.insetmode
|
|
119
|
+
};
|
|
120
|
+
};
|
|
87
121
|
export { VmlShapeXform };
|
|
@@ -21,7 +21,9 @@ class ContentTypesXform extends BaseXform {
|
|
|
21
21
|
mediaHash[imageType] = true;
|
|
22
22
|
xmlStream.leafNode("Default", {
|
|
23
23
|
Extension: imageType,
|
|
24
|
-
|
|
24
|
+
// SVG's IANA media type is "image/svg+xml"; everything else follows
|
|
25
|
+
// the "image/<ext>" convention.
|
|
26
|
+
ContentType: imageType === "svg" ? "image/svg+xml" : `image/${imageType}`
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
}
|
|
@@ -3,6 +3,7 @@ import { BaseCellAnchorXform } from "./base-cell-anchor-xform.js";
|
|
|
3
3
|
import { ExtXform } from "./ext-xform.js";
|
|
4
4
|
import { GraphicFrameXform } from "./graphic-frame-xform.js";
|
|
5
5
|
import { PicXform } from "./pic-xform.js";
|
|
6
|
+
import { ShapeXform } from "./shape-xform.js";
|
|
6
7
|
import { StaticXform } from "../static-xform.js";
|
|
7
8
|
/** https://en.wikipedia.org/wiki/Office_Open_XML_file_formats#DrawingML */
|
|
8
9
|
const EMU_PER_PIXEL_AT_96_DPI = 9525;
|
|
@@ -70,6 +71,7 @@ class AbsoluteAnchorXform extends BaseCellAnchorXform {
|
|
|
70
71
|
// `<xdr:absoluteAnchor><xdr:pos/><xdr:ext/><xdr:clientData/></xdr:absoluteAnchor>`
|
|
71
72
|
// with no chart reference, so the anchor was ignored on open.
|
|
72
73
|
"xdr:graphicFrame": new GraphicFrameXform(),
|
|
74
|
+
"xdr:userShape": new ShapeXform(),
|
|
73
75
|
"xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
|
|
74
76
|
};
|
|
75
77
|
}
|
|
@@ -94,6 +96,9 @@ class AbsoluteAnchorXform extends BaseCellAnchorXform {
|
|
|
94
96
|
else if (model.graphicFrame) {
|
|
95
97
|
this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
|
|
96
98
|
}
|
|
99
|
+
else if (model.shape?.kind === "userShape") {
|
|
100
|
+
this.map["xdr:userShape"].render(xmlStream, model.shape);
|
|
101
|
+
}
|
|
97
102
|
this.map["xdr:clientData"].render(xmlStream, {});
|
|
98
103
|
xmlStream.closeNode();
|
|
99
104
|
}
|
|
@@ -47,8 +47,25 @@ class BaseCellAnchorXform extends BaseXform {
|
|
|
47
47
|
const name = match[1];
|
|
48
48
|
const mediaId = options.mediaIndex[name];
|
|
49
49
|
const medium = options.media[mediaId];
|
|
50
|
+
if (!medium) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
// Resolve an SVG companion (asvg:svgBlip extension) back to its media
|
|
54
|
+
// index and record it on the raster media entry itself, so callers that
|
|
55
|
+
// look the image up by id (e.g. Workbook.getImage) surface the vector
|
|
56
|
+
// companion alongside the raster fallback.
|
|
57
|
+
if (model.svgRId) {
|
|
58
|
+
const svgRel = options.rels[model.svgRId];
|
|
59
|
+
const svgMatch = svgRel && svgRel.Target.match(/.*\/media\/(.+[.][a-zA-Z]{3,4})/);
|
|
60
|
+
if (svgMatch) {
|
|
61
|
+
const svgMediaId = options.mediaIndex[svgMatch[1]];
|
|
62
|
+
if (svgMediaId !== undefined) {
|
|
63
|
+
medium.svgMediaId = svgMediaId;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
// Preserve alphaModFix (transparency) from the picture model if present
|
|
51
|
-
if (
|
|
68
|
+
if (model.alphaModFix !== undefined) {
|
|
52
69
|
return { ...medium, alphaModFix: model.alphaModFix };
|
|
53
70
|
}
|
|
54
71
|
return medium;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { BaseXform } from "../base-xform.js";
|
|
2
|
+
/** OOXML extension URI for the SVG blip (Office 2016 SVG feature). */
|
|
3
|
+
const SVG_BLIP_EXT_URI = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}";
|
|
4
|
+
/** Namespace for the asvg:svgBlip element. */
|
|
5
|
+
const SVG_BLIP_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main";
|
|
6
|
+
const REL_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
2
7
|
class BlipXform extends BaseXform {
|
|
3
8
|
constructor() {
|
|
4
9
|
super();
|
|
@@ -10,23 +15,37 @@ class BlipXform extends BaseXform {
|
|
|
10
15
|
render(xmlStream, model) {
|
|
11
16
|
// External (linked) images use `r:link`; embedded images use `r:embed`.
|
|
12
17
|
const relAttr = model.external ? "r:link" : "r:embed";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
const hasAlpha = model.alphaModFix !== undefined && model.alphaModFix < 100000;
|
|
19
|
+
const hasSvg = model.svgRId !== undefined;
|
|
20
|
+
// A bare blip can be a leaf node; an alpha modulation or an SVG extension
|
|
21
|
+
// both require child elements, so switch to the open/close form.
|
|
22
|
+
if (!hasAlpha && !hasSvg) {
|
|
23
|
+
xmlStream.leafNode(this.tag, {
|
|
24
|
+
"xmlns:r": REL_NS,
|
|
17
25
|
[relAttr]: model.rId,
|
|
18
26
|
cstate: "print"
|
|
19
27
|
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
xmlStream.openNode(this.tag, {
|
|
31
|
+
"xmlns:r": REL_NS,
|
|
32
|
+
[relAttr]: model.rId,
|
|
33
|
+
cstate: "print"
|
|
34
|
+
});
|
|
35
|
+
if (hasAlpha) {
|
|
20
36
|
xmlStream.leafNode("a:alphaModFix", { amt: String(model.alphaModFix) });
|
|
21
|
-
xmlStream.closeNode();
|
|
22
37
|
}
|
|
23
|
-
|
|
24
|
-
xmlStream.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
if (hasSvg) {
|
|
39
|
+
xmlStream.openNode("a:extLst");
|
|
40
|
+
xmlStream.openNode("a:ext", { uri: SVG_BLIP_EXT_URI });
|
|
41
|
+
xmlStream.leafNode("asvg:svgBlip", {
|
|
42
|
+
"xmlns:asvg": SVG_BLIP_NS,
|
|
43
|
+
"r:embed": model.svgRId
|
|
28
44
|
});
|
|
45
|
+
xmlStream.closeNode(); // a:ext
|
|
46
|
+
xmlStream.closeNode(); // a:extLst
|
|
29
47
|
}
|
|
48
|
+
xmlStream.closeNode(); // a:blip
|
|
30
49
|
}
|
|
31
50
|
parseOpen(node) {
|
|
32
51
|
switch (node.name) {
|
|
@@ -46,6 +65,14 @@ class BlipXform extends BaseXform {
|
|
|
46
65
|
this.model.alphaModFix = parseInt(node.attributes.amt, 10);
|
|
47
66
|
}
|
|
48
67
|
return true;
|
|
68
|
+
case "asvg:svgBlip": {
|
|
69
|
+
// Capture the SVG companion's relationship id for round-trip.
|
|
70
|
+
const embed = node.attributes["r:embed"];
|
|
71
|
+
if (embed !== undefined && this.model) {
|
|
72
|
+
this.model.svgRId = embed;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
49
76
|
default:
|
|
50
77
|
return true;
|
|
51
78
|
}
|
|
@@ -56,7 +83,7 @@ class BlipXform extends BaseXform {
|
|
|
56
83
|
case this.tag:
|
|
57
84
|
return false;
|
|
58
85
|
default:
|
|
59
|
-
// unprocessed internal nodes
|
|
86
|
+
// unprocessed internal nodes (a:extLst / a:ext / alphaModFix)
|
|
60
87
|
return true;
|
|
61
88
|
}
|
|
62
89
|
}
|
|
@@ -3,6 +3,7 @@ import { CellPositionXform } from "./cell-position-xform.js";
|
|
|
3
3
|
import { ExtXform } from "./ext-xform.js";
|
|
4
4
|
import { GraphicFrameXform } from "./graphic-frame-xform.js";
|
|
5
5
|
import { PicXform } from "./pic-xform.js";
|
|
6
|
+
import { ShapeXform } from "./shape-xform.js";
|
|
6
7
|
import { StaticXform } from "../static-xform.js";
|
|
7
8
|
class OneCellAnchorXform extends BaseCellAnchorXform {
|
|
8
9
|
constructor() {
|
|
@@ -11,6 +12,7 @@ class OneCellAnchorXform extends BaseCellAnchorXform {
|
|
|
11
12
|
"xdr:from": new CellPositionXform({ tag: "xdr:from" }),
|
|
12
13
|
"xdr:ext": new ExtXform({ tag: "xdr:ext" }),
|
|
13
14
|
"xdr:pic": new PicXform(),
|
|
15
|
+
"xdr:userShape": new ShapeXform(),
|
|
14
16
|
"xdr:graphicFrame": new GraphicFrameXform(),
|
|
15
17
|
"xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
|
|
16
18
|
};
|
|
@@ -36,6 +38,9 @@ class OneCellAnchorXform extends BaseCellAnchorXform {
|
|
|
36
38
|
else if (model.graphicFrame) {
|
|
37
39
|
this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
|
|
38
40
|
}
|
|
41
|
+
else if (model.shape?.kind === "userShape") {
|
|
42
|
+
this.map["xdr:userShape"].render(xmlStream, model.shape);
|
|
43
|
+
}
|
|
39
44
|
this.map["xdr:clientData"].render(xmlStream, {});
|
|
40
45
|
xmlStream.closeNode();
|
|
41
46
|
}
|
|
@@ -25,7 +25,8 @@ class PicXform extends BaseXform {
|
|
|
25
25
|
this.map["xdr:blipFill"].render(xmlStream, {
|
|
26
26
|
rId: model.rId,
|
|
27
27
|
alphaModFix: model.alphaModFix,
|
|
28
|
-
external: model.external
|
|
28
|
+
external: model.external,
|
|
29
|
+
svgRId: model.svgRId
|
|
29
30
|
});
|
|
30
31
|
this.map["xdr:spPr"].render(xmlStream, model);
|
|
31
32
|
xmlStream.closeNode();
|