xls-codec 4.14.0 → 4.15.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.
Files changed (58) hide show
  1. package/README.md +37 -19
  2. package/dist/biff/record-writer.cjs +18 -0
  3. package/dist/biff/record-writer.d.cts +7 -1
  4. package/dist/biff/record-writer.d.ts +7 -1
  5. package/dist/biff/record-writer.js +18 -1
  6. package/dist/container.cjs +9 -1
  7. package/dist/container.d.cts +2 -0
  8. package/dist/container.d.ts +2 -0
  9. package/dist/container.js +9 -1
  10. package/dist/content.cjs +6 -5
  11. package/dist/content.js +6 -5
  12. package/dist/drawing/escher-constants.cjs +6 -0
  13. package/dist/drawing/escher-constants.d.cts +5 -1
  14. package/dist/drawing/escher-constants.d.ts +5 -1
  15. package/dist/drawing/escher-constants.js +5 -1
  16. package/dist/drawing/escher-writer.cjs +107 -0
  17. package/dist/drawing/escher-writer.d.cts +32 -0
  18. package/dist/drawing/escher-writer.d.ts +32 -0
  19. package/dist/drawing/escher-writer.js +105 -0
  20. package/dist/drawing/md4.cjs +146 -0
  21. package/dist/drawing/md4.d.cts +7 -0
  22. package/dist/drawing/md4.d.ts +7 -0
  23. package/dist/drawing/md4.js +145 -0
  24. package/dist/drawing-writer-Blm_NUSZ.d.cts +19 -0
  25. package/dist/drawing-writer-Blm_NUSZ.d.ts +19 -0
  26. package/dist/index.cjs +1 -0
  27. package/dist/index.d.cts +2 -2
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +2 -2
  30. package/dist/workbook/comments.cjs +26 -0
  31. package/dist/workbook/comments.d.cts +7 -1
  32. package/dist/workbook/comments.d.ts +7 -1
  33. package/dist/workbook/comments.js +26 -1
  34. package/dist/workbook/conditional-format-write.cjs +307 -18
  35. package/dist/workbook/conditional-format-write.js +307 -18
  36. package/dist/workbook/drawing-writer.cjs +253 -0
  37. package/dist/workbook/drawing-writer.d.cts +2 -0
  38. package/dist/workbook/drawing-writer.d.ts +2 -0
  39. package/dist/workbook/drawing-writer.js +252 -0
  40. package/dist/workbook/drawing.cjs +24 -3
  41. package/dist/workbook/drawing.d.cts +2 -0
  42. package/dist/workbook/drawing.d.ts +2 -0
  43. package/dist/workbook/drawing.js +25 -4
  44. package/dist/workbook/embedded-object.cjs +43 -0
  45. package/dist/workbook/embedded-object.d.cts +8 -0
  46. package/dist/workbook/embedded-object.d.ts +8 -0
  47. package/dist/workbook/embedded-object.js +41 -0
  48. package/dist/workbook/globals-writer.cjs +1 -0
  49. package/dist/workbook/globals-writer.d.cts +2 -0
  50. package/dist/workbook/globals-writer.d.ts +2 -0
  51. package/dist/workbook/globals-writer.js +2 -1
  52. package/dist/workbook/sheet-writer.cjs +4 -3
  53. package/dist/workbook/sheet-writer.d.cts +3 -2
  54. package/dist/workbook/sheet-writer.d.ts +3 -2
  55. package/dist/workbook/sheet-writer.js +4 -3
  56. package/dist/write.cjs +17 -7
  57. package/dist/write.js +17 -7
  58. package/package.json +1 -1
@@ -0,0 +1,252 @@
1
+ import "../biff/record-types.js";
2
+ import { DEFAULT_COLUMN_WIDTH_CHARS, columnWidthToPoints } from "../units.js";
3
+ import { RecordBuilder } from "../biff/builder.js";
4
+ import { BiffWriteError } from "../biff/write-errors.js";
5
+ import { writeRecord, writeRecordChain } from "../biff/record-writer.js";
6
+ import { writeXLUnicodeStringNoCch } from "../biff/string-writer.js";
7
+ import { writeEmbeddedObjectPackage } from "./embedded-object.js";
8
+ import { writeDrawingGroupBytes, writeSheetDrawingBytes } from "../drawing/escher-writer.js";
9
+ //#region src/workbook/drawing-writer.ts
10
+ /** The sheet-grid geometry an anchor resolves against and inverts into, the write-side mirror of workbook/drawing.ts's own SheetGridGeometry: declared column widths/row heights with the same Excel "Normal" defaults beneath, so a shape written from a given placement reads back at the identical placement. Derived from the same constants (units.ts) the reader's own geometry uses, so the two cannot disagree about what an undeclared cell sizes. */
11
+ var WriterGridGeometry = class {
12
+ columnWidths = /* @__PURE__ */ new Map();
13
+ rowHeights = /* @__PURE__ */ new Map();
14
+ defaultColumnWidthPt = columnWidthToPoints(DEFAULT_COLUMN_WIDTH_CHARS * 256);
15
+ constructor(sheet) {
16
+ for (const column of sheet.columns) if (column.widthPt !== void 0) this.columnWidths.set(column.index, column.widthPt);
17
+ for (const row of sheet.rows) if (row.heightPt !== void 0) this.rowHeights.set(row.index, row.heightPt);
18
+ }
19
+ columnWidthPt(index) {
20
+ return this.columnWidths.get(index) ?? this.defaultColumnWidthPt;
21
+ }
22
+ rowHeightPt(index) {
23
+ return this.rowHeights.get(index) ?? 15;
24
+ }
25
+ /** The absolute x of a column's own left edge -- the cumulative width of every column before it, the identical accumulation the reader's own geometry walks back down. */
26
+ xPt(column) {
27
+ let x = 0;
28
+ for (let index = 0; index < column; index += 1) x += this.columnWidthPt(index);
29
+ return x;
30
+ }
31
+ yPt(row) {
32
+ let y = 0;
33
+ for (let index = 0; index < row; index += 1) y += this.rowHeightPt(index);
34
+ return y;
35
+ }
36
+ /** Locates an absolute x as a column plus a 1/1024ths-of-that-column fraction, the pair OfficeArtClientAnchorSheet's own left/right corners state. A point beyond the grid's own last column clamps to that column's far edge: the grid has no column 256 to name, and a shape whose extent runs that far past the grid loses only the overflow, where refusing the workbook would lose the cells too -- the same trade a print range past the grid already draws (print-names.ts's clampToGrid). */
37
+ locateX(x) {
38
+ let left = 0;
39
+ for (let column = 0; column < 255; column += 1) {
40
+ const width = this.columnWidthPt(column);
41
+ if (x < left + width) return {
42
+ column,
43
+ fraction: Math.min(1023, Math.round((x - left) / width * 1024))
44
+ };
45
+ left += width;
46
+ }
47
+ return {
48
+ column: 255,
49
+ fraction: 1023
50
+ };
51
+ }
52
+ /** The row-axis counterpart: a row plus a 1/256ths-of-that-row fraction, clamped to the grid's own last row. */
53
+ locateY(y) {
54
+ let top = 0;
55
+ for (let row = 0; row < 65535; row += 1) {
56
+ const height = this.rowHeightPt(row);
57
+ if (y < top + height) return {
58
+ row,
59
+ fraction: Math.min(255, Math.round((y - top) / height * 256))
60
+ };
61
+ top += height;
62
+ }
63
+ return {
64
+ row: 65535,
65
+ fraction: 255
66
+ };
67
+ }
68
+ };
69
+ function placementOfImage(image, geometry) {
70
+ if (image.anchorRow > 65535 || image.anchorColumn > 255) throw new BiffWriteError(`a sheet image anchored at row ${image.anchorRow}, column ${image.anchorColumn} is outside BIFF8's own grid (rows 0-65535, columns 0-255); a .xls workbook cannot address the cell it names`);
71
+ return {
72
+ startXPt: geometry.xPt(image.anchorColumn) + image.offsetXPt,
73
+ startYPt: geometry.yPt(image.anchorRow) + image.offsetYPt,
74
+ widthPt: image.widthPt,
75
+ heightPt: image.heightPt
76
+ };
77
+ }
78
+ function placementOfEmbedded(embedded, geometry) {
79
+ return {
80
+ startXPt: embedded.anchorColumn !== void 0 && embedded.offsetXPt !== void 0 ? geometry.xPt(embedded.anchorColumn) + embedded.offsetXPt : embedded.frame.xPt,
81
+ startYPt: embedded.anchorRow !== void 0 && embedded.offsetYPt !== void 0 ? geometry.yPt(embedded.anchorRow) + embedded.offsetYPt : embedded.frame.yPt,
82
+ widthPt: embedded.frame.widthPt,
83
+ heightPt: embedded.frame.heightPt
84
+ };
85
+ }
86
+ /** Inverts a placement into the OfficeArtClientAnchorSheet corner pair the reader's own resolveAnchorPlacement turns back into that placement: each corner resolved to its containing cell and a 1/1024ths (columns) or 1/256ths (rows) fraction within it. */
87
+ function anchorOf(placement, geometry) {
88
+ const start = geometry.locateX(placement.startXPt);
89
+ const end = geometry.locateX(placement.startXPt + placement.widthPt);
90
+ const top = geometry.locateY(placement.startYPt);
91
+ const bottom = geometry.locateY(placement.startYPt + placement.heightPt);
92
+ return {
93
+ colL: start.column,
94
+ dxL: start.fraction,
95
+ rwT: top.row,
96
+ dyT: top.fraction,
97
+ colR: end.column,
98
+ dxR: end.fraction,
99
+ rwB: bottom.row,
100
+ dyB: bottom.fraction
101
+ };
102
+ }
103
+ /** [MS-XLS] 2.5.213's own ot table: the Picture type both a plain image and an embedded OLE object carry -- an OLE object IS hosted through the picture machinery (its FtPictFmla naming the Embedding Storage its data lives in), which is exactly how the Embedding Storage page itself states the pairing ("cmo.ot equal to 8, pictFlags.fPrstm equal to 0, and pictFlags.fDde equal to 0"). */
104
+ const OBJECT_TYPE_PICTURE = 8;
105
+ /** FtCmo ([MS-XLS] 2.5.92, 22 bytes): ft 0x15, cb 0x12, the object type and id, then grbit and three unused dwords all written zero -- the identical shape comment-writer.ts writes for a Note, restated here with the object type as a parameter rather than shared across the two direction modules. */
106
+ function writeFtCmo(ot, id) {
107
+ return new RecordBuilder().u16(21).u16(18).u16(ot).u16(id).u16(0).u32(0).u32(0).u32(0).build();
108
+ }
109
+ /** FtCf ([MS-XLS] 2.5.142, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/fc5bb3ce-8e35-4393-b22f-9cf54062a3a4): the clipboard format of the picture this object shows. 0xFFFF names "an unspecified format that is neither an enhanced metafile nor a bitmap" -- honest for a shape whose visible rendering is the blip the Escher layer itself carries and for an OLE object this writer has no preview metafile for. */
110
+ function writeFtCf() {
111
+ return new RecordBuilder().u16(7).u16(2).u16(65535).build();
112
+ }
113
+ /** FtPioGrbit ([MS-XLS] 2.5.151, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/8eee0b3d-9d27-4294-85fc-a66ae8a361c9): a plain picture states fAutoPict (aspect preserved across views); an OLE embedding states no bits at all -- fPrstm and fDde stay clear, the pair the Embedding Storage page requires for storage-based object data. */
114
+ function writeFtPioGrbit(autoPict) {
115
+ return new RecordBuilder().u16(8).u16(2).u16(autoPict ? 1 : 0).build();
116
+ }
117
+ /** The PtgTbl token byte an embedded object's ObjectParsedFormula carries ([MS-XLS] 2.5.198.92: ptg 0x02, class none) -- the spelling that tells a reader this picture's data lives in an Embedding Storage rather than a linked range. */
118
+ const PTG_TBL = 2;
119
+ /** The class name stated in an embedding's PictFmlaEmbedInfo: "Package" is what a genuine OLE Package embed carries, so a real OLE-aware consumer that cannot decode this package's own JSON payload still sees a recognisable, accurate class rather than an invented one -- the identical choice rtf-codec's own ObjectHeader makes for the same payload shape. */
120
+ const EMBED_CLASS_NAME = "Package";
121
+ /** FtPictFmla ([MS-XLS] 2.5.150, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/00f89d32-67b0-408e-9eaf-f4fecbddb089) for an embedded OLE object: the ObjFmla (cbFmla counting the ObjectParsedFormula, the PictFmlaEmbedInfo, and the padding -- even, per [MS-XLS] 2.5.187's own cbFmla rule), then lPosInCtlStm, the storage id the Embedding Storage's own MBD name is the eight-hex-digit spelling of. The ObjectParsedFormula is the one shape [MS-XLS] pins for an embedding: cce 5, rgce one PtgTbl followed by four undefined bytes. */
122
+ function writeFtPictFmla(storageId) {
123
+ const formula = new RecordBuilder().u16(5).u32(0).u8(PTG_TBL).bytes(/* @__PURE__ */ new Uint8Array(4)).build();
124
+ const className = writeXLUnicodeStringNoCch(EMBED_CLASS_NAME);
125
+ const embedInfo = new RecordBuilder().u8(3).u8(className.length - 1).u8(0).bytes(className).build();
126
+ const fmlaBytes = new RecordBuilder().bytes(formula).bytes(embedInfo).build();
127
+ const data = new RecordBuilder().u16(fmlaBytes.length).bytes(fmlaBytes).u32(storageId).build();
128
+ return new RecordBuilder().u16(9).u16(data.length).bytes(data).build();
129
+ }
130
+ /** The trailing four reserved bytes every Obj not naming a list-box/dropdown object carries ([MS-XLS] 2.4.181's own reserved field: MUST be 0) -- the ftEnd marker a real sub-record walk terminates on. */
131
+ const OBJ_RESERVED_END = /* @__PURE__ */ new Uint8Array(4);
132
+ /** One picture shape's Obj record: FtCmo (ot Picture), FtCf, FtPioGrbit, and the trailing reserved field. No FtPictFmla -- the image's bytes live in the workbook's Blip Store, which the shape's own pib property names, leaving the Obj record itself nothing to locate. */
133
+ function writePictureObjRecord(objectId) {
134
+ return writeRecord(93, new RecordBuilder().bytes(writeFtCmo(OBJECT_TYPE_PICTURE, objectId)).bytes(writeFtCf()).bytes(writeFtPioGrbit(true)).bytes(OBJ_RESERVED_END).build());
135
+ }
136
+ /** One embedded OLE object's Obj record: FtCmo (ot Picture), FtCf, FtPioGrbit (no bits -- storage-based, per the Embedding Storage page's own fPrstm/fDde requirement), the FtPictFmla naming the storage, and the trailing reserved field. */
137
+ function writeEmbeddedObjRecord(objectId, storageId) {
138
+ return writeRecord(93, new RecordBuilder().bytes(writeFtCmo(OBJECT_TYPE_PICTURE, objectId)).bytes(writeFtCf()).bytes(writeFtPioGrbit(false)).bytes(writeFtPictFmla(storageId)).bytes(OBJ_RESERVED_END).build());
139
+ }
140
+ /** Base64's own character set, decoded by hand rather than through atob's DOM-string round trip -- mirroring drawing/blips.ts's own hand-written encoder, which exists for the identical reason: byte-exact, allocation-predictable, and identical in Node and a Workers isolate. */
141
+ function bytesFromBase64(base64) {
142
+ const values = (/* @__PURE__ */ new Int8Array(256)).fill(-1);
143
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("").forEach((char, index) => {
144
+ values[char.charCodeAt(0)] = index;
145
+ });
146
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
147
+ const out = new Uint8Array(base64.length / 4 * 3 - padding);
148
+ let buffer = 0;
149
+ let bits = 0;
150
+ let outIndex = 0;
151
+ for (const char of base64) {
152
+ const value = values[char.charCodeAt(0)];
153
+ if (value === void 0 || value < 0) continue;
154
+ buffer = buffer << 6 | value;
155
+ bits += 6;
156
+ if (bits >= 8) {
157
+ bits -= 8;
158
+ out[outIndex] = buffer >> bits & 255;
159
+ outIndex += 1;
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+ function buildDrawingWritePlan(sheets) {
165
+ const blips = [];
166
+ const blipIndexByBase64 = /* @__PURE__ */ new Map();
167
+ const resolveBlip = (image) => {
168
+ if (image.format !== "png" && image.format !== "jpeg") throw new BiffWriteError(`xls-codec cannot write a sheet image of format "${image.format}": [MS-ODRAW]'s own MSOBLIPTYPE enumeration has no member for it, so no Blip Store entry can carry it`);
169
+ const existing = blipIndexByBase64.get(image.base64);
170
+ if (existing !== void 0) {
171
+ const blip = blips[existing - 1];
172
+ if (blip === void 0) throw new BiffWriteError("internal error: a blip index resolved that the workbook-wide image scan never assigned");
173
+ blip.referenceCount += 1;
174
+ return existing;
175
+ }
176
+ const index = blips.length + 1;
177
+ blipIndexByBase64.set(image.base64, index);
178
+ blips.push({
179
+ format: image.format,
180
+ fileBytes: bytesFromBase64(image.base64),
181
+ referenceCount: 1
182
+ });
183
+ return index;
184
+ };
185
+ let nextStorageId = 1;
186
+ const storageIdOf = () => {
187
+ const id = nextStorageId;
188
+ nextStorageId += 1;
189
+ return id;
190
+ };
191
+ let nextSpid = 1024;
192
+ let nextDrawingId = 1;
193
+ const drawingBlocks = [];
194
+ const sheetDrawings = [];
195
+ const embeddingStreams = [];
196
+ for (const sheet of sheets) {
197
+ const geometry = new WriterGridGeometry(sheet);
198
+ const entries = [];
199
+ const objRecords = [];
200
+ let nextObjectId = sheet.cells.filter((cell) => cell.comment !== void 0).length + 1;
201
+ for (const image of sheet.images) {
202
+ entries.push({
203
+ anchor: anchorOf(placementOfImage(image, geometry), geometry),
204
+ blipIndex: resolveBlip(image),
205
+ oleShape: false
206
+ });
207
+ objRecords.push(writePictureObjRecord(nextObjectId));
208
+ nextObjectId += 1;
209
+ }
210
+ for (const embedded of sheet.embeddedObjects ?? []) {
211
+ if (embedded.objectKind === "chart") throw new BiffWriteError("xls-codec cannot write a 'chart' embedded object: embedding one means writing a genuine BIFF8 chart substream -- the whole [MS-XLS] chart grammar its series data links drive -- which is a chart engine of its own rather than a container for the flattened series table the schema carries");
212
+ const storageId = storageIdOf();
213
+ entries.push({
214
+ anchor: anchorOf(placementOfEmbedded(embedded, geometry), geometry),
215
+ blipIndex: void 0,
216
+ oleShape: true
217
+ });
218
+ objRecords.push(writeEmbeddedObjRecord(nextObjectId, storageId));
219
+ nextObjectId += 1;
220
+ embeddingStreams.push({
221
+ path: `MBD${storageId.toString(16).toUpperCase().padStart(8, "0")}/Package`,
222
+ bytes: writeEmbeddedObjectPackage(embedded)
223
+ });
224
+ }
225
+ if (entries.length === 0) {
226
+ sheetDrawings.push({
227
+ msoDrawingRecords: [],
228
+ objRecords: []
229
+ });
230
+ continue;
231
+ }
232
+ const escherBytes = writeSheetDrawingBytes(nextDrawingId, nextSpid, entries);
233
+ drawingBlocks.push({
234
+ drawingId: nextDrawingId,
235
+ lastSpid: nextSpid + entries.length,
236
+ shapeCount: entries.length + 1
237
+ });
238
+ nextDrawingId += 1;
239
+ nextSpid += entries.length + 1;
240
+ sheetDrawings.push({
241
+ msoDrawingRecords: writeRecordChain(236, escherBytes),
242
+ objRecords
243
+ });
244
+ }
245
+ return {
246
+ drawingGroupBytes: drawingBlocks.length > 0 ? writeDrawingGroupBytes(blips, drawingBlocks) : void 0,
247
+ sheetDrawings,
248
+ embeddingStreams
249
+ };
250
+ }
251
+ //#endregion
252
+ export { buildDrawingWritePlan };
@@ -6,6 +6,7 @@ const require_drawing_bytes = require("../drawing/bytes.cjs");
6
6
  const require_workbook_comments = require("./comments.cjs");
7
7
  const require_drawing_shapes = require("../drawing/shapes.cjs");
8
8
  const require_workbook_chart = require("./chart.cjs");
9
+ const require_workbook_embedded_object = require("./embedded-object.cjs");
9
10
  let document_schema_js = require("document-schema.js");
10
11
  //#region src/workbook/drawing.ts
11
12
  /** [MS-XLS] "FtCmo" ot enumeration, the values this reader routes on. */
@@ -69,8 +70,7 @@ function readSheetDrawing(worksheetRecords, context) {
69
70
  const record = worksheetRecords[index];
70
71
  if (record === void 0) continue;
71
72
  if (record.type === 236) {
72
- const block = record.blocks[0];
73
- if (block !== void 0) drawingChunks.push(block);
73
+ drawingChunks.push(...record.blocks);
74
74
  continue;
75
75
  }
76
76
  if (record.type === 93) {
@@ -79,7 +79,8 @@ function readSheetDrawing(worksheetRecords, context) {
79
79
  objEntries.push({
80
80
  ot,
81
81
  offset: record.offset,
82
- nextOffset
82
+ nextOffset,
83
+ group: record
83
84
  });
84
85
  }
85
86
  }
@@ -98,6 +99,11 @@ function readSheetDrawing(worksheetRecords, context) {
98
99
  const obj = objEntries[index];
99
100
  if (shape === void 0 || obj === void 0 || obj.ot === OBJECT_TYPE_NOTE) continue;
100
101
  if (obj.ot === OBJECT_TYPE_PICTURE || shape.shapeType === 75) {
102
+ const embedded = embeddedObjectFromObjRecord(obj.group, shape, context, geometry);
103
+ if (embedded !== void 0) {
104
+ embeddedObjects.push(embedded);
105
+ continue;
106
+ }
101
107
  const image = imageFromShape(shape, context, geometry);
102
108
  if (image !== void 0) images.push(image);
103
109
  continue;
@@ -115,6 +121,21 @@ function readSheetDrawing(worksheetRecords, context) {
115
121
  embeddedObjects
116
122
  };
117
123
  }
124
+ /** A Picture-type Obj record whose FtPictFmla names an Embedding Storage this workbook's own outer compound file carries: resolved through readEmbeddedObjectPackage rather than the plain Blip Store path imageFromShape covers, since an OLE-embedded object's data lives in that storage's own Package stream instead of a pib reference into the workbook-wide Blip Store. Undefined for a plain picture (no FtPictFmla at all), an FtPictFmla naming a storage id this workbook's container did not report, or a storage whose Package stream is not this codec's own payload (readEmbeddedObjectPackage's own foreign-payload degrade) -- each falls through to imageFromShape instead. */
125
+ function embeddedObjectFromObjRecord(objGroup, shape, context, geometry) {
126
+ const storageId = require_workbook_comments.readObjPictFmlaStorageId(objGroup);
127
+ if (storageId === void 0) return;
128
+ const packageBytes = context.embeddingStreams.get(storageId);
129
+ if (packageBytes === void 0) return;
130
+ const placement = resolveAnchorPlacement(shape.anchor, geometry);
131
+ if (placement.widthPt <= 0 || placement.heightPt <= 0) return;
132
+ return require_workbook_embedded_object.readEmbeddedObjectPackage(packageBytes, {
133
+ xPt: placement.xPt,
134
+ yPt: placement.yPt,
135
+ widthPt: placement.widthPt,
136
+ heightPt: placement.heightPt
137
+ });
138
+ }
118
139
  function imageFromShape(shape, context, geometry) {
119
140
  if (shape.blipIndex === void 0) return;
120
141
  const blip = context.blipStore.get(shape.blipIndex);
@@ -18,6 +18,8 @@ interface SheetDrawingContext {
18
18
  readonly metadata: LayoutMetadata;
19
19
  /** Every substream the workbook stream carries -- searched for the chart substream a Chart-type Obj record's own nested BOF...EOF produced (splitSubstreams reports it as its own entry, positioned by byte offset rather than nested inside the worksheet's own `records`; see biff/substreams.ts's own top comment for why). */
20
20
  readonly allSubstreams: readonly Substream[];
21
+ /** Every "MBD<hex>/Package" Embedding Storage the outer compound file carries, keyed by the storage id a Picture-type Obj record's own FtPictFmla names (container.ts's own readWorkbookStreams) -- resolved here for a Picture Obj record whose data lives in an OLE embedding rather than the workbook-wide Blip Store. */
22
+ readonly embeddingStreams: ReadonlyMap<number, Uint8Array<ArrayBuffer>>;
21
23
  }
22
24
  /** Reads one worksheet's own drawing content: every MsoDrawing record's bytes concatenated into one Escher stream (drawing/shapes.ts), each of its real top-level shapes paired 1:1, in document order, with the non-Note Obj records the same substream carries. */
23
25
  declare function readSheetDrawing(worksheetRecords: readonly RecordGroup[], context: SheetDrawingContext): SheetDrawing;
@@ -18,6 +18,8 @@ interface SheetDrawingContext {
18
18
  readonly metadata: LayoutMetadata;
19
19
  /** Every substream the workbook stream carries -- searched for the chart substream a Chart-type Obj record's own nested BOF...EOF produced (splitSubstreams reports it as its own entry, positioned by byte offset rather than nested inside the worksheet's own `records`; see biff/substreams.ts's own top comment for why). */
20
20
  readonly allSubstreams: readonly Substream[];
21
+ /** Every "MBD<hex>/Package" Embedding Storage the outer compound file carries, keyed by the storage id a Picture-type Obj record's own FtPictFmla names (container.ts's own readWorkbookStreams) -- resolved here for a Picture Obj record whose data lives in an OLE embedding rather than the workbook-wide Blip Store. */
22
+ readonly embeddingStreams: ReadonlyMap<number, Uint8Array<ArrayBuffer>>;
21
23
  }
22
24
  /** Reads one worksheet's own drawing content: every MsoDrawing record's bytes concatenated into one Escher stream (drawing/shapes.ts), each of its real top-level shapes paired 1:1, in document order, with the non-Note Obj records the same substream carries. */
23
25
  declare function readSheetDrawing(worksheetRecords: readonly RecordGroup[], context: SheetDrawingContext): SheetDrawing;
@@ -2,9 +2,10 @@ import "../biff/record-types.js";
2
2
  import { DEFAULT_COLUMN_WIDTH_CHARS, columnWidthToPoints } from "../units.js";
3
3
  import "../drawing/escher-constants.js";
4
4
  import { concatBytes } from "../drawing/bytes.js";
5
- import { readObjTypeAndId } from "./comments.js";
5
+ import { readObjPictFmlaStorageId, readObjTypeAndId } from "./comments.js";
6
6
  import { readSheetShapes } from "../drawing/shapes.js";
7
7
  import { readChartSeries } from "./chart.js";
8
+ import { readEmbeddedObjectPackage } from "./embedded-object.js";
8
9
  import { PAGE_SIZE_LETTER } from "document-schema.js";
9
10
  //#region src/workbook/drawing.ts
10
11
  /** [MS-XLS] "FtCmo" ot enumeration, the values this reader routes on. */
@@ -68,8 +69,7 @@ function readSheetDrawing(worksheetRecords, context) {
68
69
  const record = worksheetRecords[index];
69
70
  if (record === void 0) continue;
70
71
  if (record.type === 236) {
71
- const block = record.blocks[0];
72
- if (block !== void 0) drawingChunks.push(block);
72
+ drawingChunks.push(...record.blocks);
73
73
  continue;
74
74
  }
75
75
  if (record.type === 93) {
@@ -78,7 +78,8 @@ function readSheetDrawing(worksheetRecords, context) {
78
78
  objEntries.push({
79
79
  ot,
80
80
  offset: record.offset,
81
- nextOffset
81
+ nextOffset,
82
+ group: record
82
83
  });
83
84
  }
84
85
  }
@@ -97,6 +98,11 @@ function readSheetDrawing(worksheetRecords, context) {
97
98
  const obj = objEntries[index];
98
99
  if (shape === void 0 || obj === void 0 || obj.ot === OBJECT_TYPE_NOTE) continue;
99
100
  if (obj.ot === OBJECT_TYPE_PICTURE || shape.shapeType === 75) {
101
+ const embedded = embeddedObjectFromObjRecord(obj.group, shape, context, geometry);
102
+ if (embedded !== void 0) {
103
+ embeddedObjects.push(embedded);
104
+ continue;
105
+ }
100
106
  const image = imageFromShape(shape, context, geometry);
101
107
  if (image !== void 0) images.push(image);
102
108
  continue;
@@ -114,6 +120,21 @@ function readSheetDrawing(worksheetRecords, context) {
114
120
  embeddedObjects
115
121
  };
116
122
  }
123
+ /** A Picture-type Obj record whose FtPictFmla names an Embedding Storage this workbook's own outer compound file carries: resolved through readEmbeddedObjectPackage rather than the plain Blip Store path imageFromShape covers, since an OLE-embedded object's data lives in that storage's own Package stream instead of a pib reference into the workbook-wide Blip Store. Undefined for a plain picture (no FtPictFmla at all), an FtPictFmla naming a storage id this workbook's container did not report, or a storage whose Package stream is not this codec's own payload (readEmbeddedObjectPackage's own foreign-payload degrade) -- each falls through to imageFromShape instead. */
124
+ function embeddedObjectFromObjRecord(objGroup, shape, context, geometry) {
125
+ const storageId = readObjPictFmlaStorageId(objGroup);
126
+ if (storageId === void 0) return;
127
+ const packageBytes = context.embeddingStreams.get(storageId);
128
+ if (packageBytes === void 0) return;
129
+ const placement = resolveAnchorPlacement(shape.anchor, geometry);
130
+ if (placement.widthPt <= 0 || placement.heightPt <= 0) return;
131
+ return readEmbeddedObjectPackage(packageBytes, {
132
+ xPt: placement.xPt,
133
+ yPt: placement.yPt,
134
+ widthPt: placement.widthPt,
135
+ heightPt: placement.heightPt
136
+ });
137
+ }
117
138
  function imageFromShape(shape, context, geometry) {
118
139
  if (shape.blipIndex === void 0) return;
119
140
  const blip = context.blipStore.get(shape.blipIndex);
@@ -0,0 +1,43 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let archive_codec = require("archive-codec");
3
+ let document_schema_js = require("document-schema.js");
4
+ //#region src/workbook/embedded-object.ts
5
+ const PACKAGE_LABEL = "xls-codec-embedded-object.json";
6
+ function payloadOf(embedded) {
7
+ return {
8
+ objectKind: embedded.objectKind,
9
+ document: embedded.document,
10
+ source: embedded.source
11
+ };
12
+ }
13
+ /** The Package stream bytes for one embedded object: this package's own JSON serialisation of the object's kind, document, and residue, wrapped in the [MS-OLEDS] packaging. */
14
+ function writeEmbeddedObjectPackage(embedded) {
15
+ const fileBytes = new TextEncoder().encode(JSON.stringify(payloadOf(embedded)));
16
+ return (0, archive_codec.writeOlePackage)({
17
+ label: PACKAGE_LABEL,
18
+ sourcePath: "",
19
+ tempPath: "",
20
+ fileBytes
21
+ });
22
+ }
23
+ /** The inverse of writeEmbeddedObjectPackage: recovers a ContentEmbeddedObject's kind, document, and residue when the Package stream bytes are this package's own payload, or undefined for anything else -- a real OLE object's packaged bytes included -- rather than throwing, since one unreadable embedding must not fail the whole sheet's drawing read. `frame` is the placement the caller derived from the embedding's own Escher anchor: the payload deliberately carries no placement of its own (see payloadOf above), and the full ContentEmbeddedObjectSchema validation needs a frame to accept, so the anchor-derived one is merged in before the parse -- the anchor stays the single authority for where the object sits. */
24
+ function readEmbeddedObjectPackage(packageBytes, frame) {
25
+ try {
26
+ const olePackage = (0, archive_codec.readOlePackage)(packageBytes);
27
+ if (olePackage.label !== PACKAGE_LABEL) return;
28
+ const text = new TextDecoder("utf-8").decode(olePackage.fileBytes);
29
+ const parsed = JSON.parse(text);
30
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || !("objectKind" in parsed) || !("document" in parsed)) return;
31
+ const result = document_schema_js.ContentEmbeddedObjectSchema.safeParse({
32
+ ...parsed,
33
+ frame
34
+ });
35
+ if (!result.success) return;
36
+ return result.data;
37
+ } catch {
38
+ return;
39
+ }
40
+ }
41
+ //#endregion
42
+ exports.readEmbeddedObjectPackage = readEmbeddedObjectPackage;
43
+ exports.writeEmbeddedObjectPackage = writeEmbeddedObjectPackage;
@@ -0,0 +1,8 @@
1
+ import { ContentEmbeddedObject } from "document-schema.js";
2
+ //#region src/workbook/embedded-object.d.ts
3
+ /** The Package stream bytes for one embedded object: this package's own JSON serialisation of the object's kind, document, and residue, wrapped in the [MS-OLEDS] packaging. */
4
+ declare function writeEmbeddedObjectPackage(embedded: ContentEmbeddedObject): Uint8Array<ArrayBuffer>;
5
+ /** The inverse of writeEmbeddedObjectPackage: recovers a ContentEmbeddedObject's kind, document, and residue when the Package stream bytes are this package's own payload, or undefined for anything else -- a real OLE object's packaged bytes included -- rather than throwing, since one unreadable embedding must not fail the whole sheet's drawing read. `frame` is the placement the caller derived from the embedding's own Escher anchor: the payload deliberately carries no placement of its own (see payloadOf above), and the full ContentEmbeddedObjectSchema validation needs a frame to accept, so the anchor-derived one is merged in before the parse -- the anchor stays the single authority for where the object sits. */
6
+ declare function readEmbeddedObjectPackage(packageBytes: Uint8Array<ArrayBuffer>, frame: ContentEmbeddedObject["frame"]): ContentEmbeddedObject | undefined;
7
+ //#endregion
8
+ export { readEmbeddedObjectPackage, writeEmbeddedObjectPackage };
@@ -0,0 +1,8 @@
1
+ import { ContentEmbeddedObject } from "document-schema.js";
2
+ //#region src/workbook/embedded-object.d.ts
3
+ /** The Package stream bytes for one embedded object: this package's own JSON serialisation of the object's kind, document, and residue, wrapped in the [MS-OLEDS] packaging. */
4
+ declare function writeEmbeddedObjectPackage(embedded: ContentEmbeddedObject): Uint8Array<ArrayBuffer>;
5
+ /** The inverse of writeEmbeddedObjectPackage: recovers a ContentEmbeddedObject's kind, document, and residue when the Package stream bytes are this package's own payload, or undefined for anything else -- a real OLE object's packaged bytes included -- rather than throwing, since one unreadable embedding must not fail the whole sheet's drawing read. `frame` is the placement the caller derived from the embedding's own Escher anchor: the payload deliberately carries no placement of its own (see payloadOf above), and the full ContentEmbeddedObjectSchema validation needs a frame to accept, so the anchor-derived one is merged in before the parse -- the anchor stays the single authority for where the object sits. */
6
+ declare function readEmbeddedObjectPackage(packageBytes: Uint8Array<ArrayBuffer>, frame: ContentEmbeddedObject["frame"]): ContentEmbeddedObject | undefined;
7
+ //#endregion
8
+ export { readEmbeddedObjectPackage, writeEmbeddedObjectPackage };
@@ -0,0 +1,41 @@
1
+ import { readOlePackage, writeOlePackage } from "archive-codec";
2
+ import { ContentEmbeddedObjectSchema } from "document-schema.js";
3
+ //#region src/workbook/embedded-object.ts
4
+ const PACKAGE_LABEL = "xls-codec-embedded-object.json";
5
+ function payloadOf(embedded) {
6
+ return {
7
+ objectKind: embedded.objectKind,
8
+ document: embedded.document,
9
+ source: embedded.source
10
+ };
11
+ }
12
+ /** The Package stream bytes for one embedded object: this package's own JSON serialisation of the object's kind, document, and residue, wrapped in the [MS-OLEDS] packaging. */
13
+ function writeEmbeddedObjectPackage(embedded) {
14
+ const fileBytes = new TextEncoder().encode(JSON.stringify(payloadOf(embedded)));
15
+ return writeOlePackage({
16
+ label: PACKAGE_LABEL,
17
+ sourcePath: "",
18
+ tempPath: "",
19
+ fileBytes
20
+ });
21
+ }
22
+ /** The inverse of writeEmbeddedObjectPackage: recovers a ContentEmbeddedObject's kind, document, and residue when the Package stream bytes are this package's own payload, or undefined for anything else -- a real OLE object's packaged bytes included -- rather than throwing, since one unreadable embedding must not fail the whole sheet's drawing read. `frame` is the placement the caller derived from the embedding's own Escher anchor: the payload deliberately carries no placement of its own (see payloadOf above), and the full ContentEmbeddedObjectSchema validation needs a frame to accept, so the anchor-derived one is merged in before the parse -- the anchor stays the single authority for where the object sits. */
23
+ function readEmbeddedObjectPackage(packageBytes, frame) {
24
+ try {
25
+ const olePackage = readOlePackage(packageBytes);
26
+ if (olePackage.label !== PACKAGE_LABEL) return;
27
+ const text = new TextDecoder("utf-8").decode(olePackage.fileBytes);
28
+ const parsed = JSON.parse(text);
29
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || !("objectKind" in parsed) || !("document" in parsed)) return;
30
+ const result = ContentEmbeddedObjectSchema.safeParse({
31
+ ...parsed,
32
+ frame
33
+ });
34
+ if (!result.success) return;
35
+ return result.data;
36
+ } catch {
37
+ return;
38
+ }
39
+ }
40
+ //#endregion
41
+ export { readEmbeddedObjectPackage, writeEmbeddedObjectPackage };
@@ -110,6 +110,7 @@ function buildWorkbookGlobals(plan) {
110
110
  for (const record of require_workbook_print_names.writePrintNameRecords(plan.printNames)) push(record);
111
111
  for (const record of require_workbook_defined_names.writeDefinedNameRecords(plan.definedNames)) push(record);
112
112
  }
113
+ if (plan.drawingGroupBytes !== void 0) for (const record of require_biff_record_writer.writeRecordChain(235, plan.drawingGroupBytes)) push(record);
113
114
  if (plan.sharedStrings.length > 0) push(writeSstRecord(plan.sharedStrings, plan.sharedStringTotalCount));
114
115
  push(require_biff_record_writer.writeRecord(10, /* @__PURE__ */ new Uint8Array(0)));
115
116
  return {
@@ -35,6 +35,8 @@ interface WorkbookGlobalsPlan {
35
35
  readonly printNames: readonly PrintNamePlanEntry[];
36
36
  /** The document-level defined names the workbook declares, one Lbl record each, from workbook/defined-names.ts's own compile of the document's names array. */
37
37
  readonly definedNames: readonly DefinedNamePlanEntry[];
38
+ /** The workbook-wide Escher drawing group ([MS-ODRAW] OfficeArtDggContainer, drawing/escher-writer.ts's own writeDrawingGroupBytes) -- the FDGGBlock plus the Blip Store every sheet's picture shapes share -- or undefined when no sheet in the workbook carries an image or embedded object, in which case no MsoDrawingGroup record is written at all. */
39
+ readonly drawingGroupBytes?: Uint8Array<ArrayBuffer>;
38
40
  }
39
41
  interface WorkbookGlobalsBuild {
40
42
  readonly bytes: Uint8Array<ArrayBuffer>;
@@ -35,6 +35,8 @@ interface WorkbookGlobalsPlan {
35
35
  readonly printNames: readonly PrintNamePlanEntry[];
36
36
  /** The document-level defined names the workbook declares, one Lbl record each, from workbook/defined-names.ts's own compile of the document's names array. */
37
37
  readonly definedNames: readonly DefinedNamePlanEntry[];
38
+ /** The workbook-wide Escher drawing group ([MS-ODRAW] OfficeArtDggContainer, drawing/escher-writer.ts's own writeDrawingGroupBytes) -- the FDGGBlock plus the Blip Store every sheet's picture shapes share -- or undefined when no sheet in the workbook carries an image or embedded object, in which case no MsoDrawingGroup record is written at all. */
39
+ readonly drawingGroupBytes?: Uint8Array<ArrayBuffer>;
38
40
  }
39
41
  interface WorkbookGlobalsBuild {
40
42
  readonly bytes: Uint8Array<ArrayBuffer>;
@@ -1,6 +1,6 @@
1
1
  import { RECORD_BOF } from "../biff/record-types.js";
2
2
  import { RecordBuilder } from "../biff/builder.js";
3
- import { concatRecords, writeRecord } from "../biff/record-writer.js";
3
+ import { concatRecords, writeRecord, writeRecordChain } from "../biff/record-writer.js";
4
4
  import { writeFontRecord } from "../biff/font.js";
5
5
  import { writeRichExtendedString, writeShortXLUnicodeString } from "../biff/string-writer.js";
6
6
  import { writeDefinedNameRecords } from "./defined-names.js";
@@ -109,6 +109,7 @@ function buildWorkbookGlobals(plan) {
109
109
  for (const record of writePrintNameRecords(plan.printNames)) push(record);
110
110
  for (const record of writeDefinedNameRecords(plan.definedNames)) push(record);
111
111
  }
112
+ if (plan.drawingGroupBytes !== void 0) for (const record of writeRecordChain(235, plan.drawingGroupBytes)) push(record);
112
113
  if (plan.sharedStrings.length > 0) push(writeSstRecord(plan.sharedStrings, plan.sharedStringTotalCount));
113
114
  push(writeRecord(10, /* @__PURE__ */ new Uint8Array(0)));
114
115
  return {
@@ -289,8 +289,8 @@ function writeFormulaRecords(cell, xfIndex) {
289
289
  function writeCellRecords(cell, xfIndex, ctx) {
290
290
  return cell.formula !== void 0 ? writeFormulaRecords(cell, xfIndex) : [writeCellValueRecord(cell, xfIndex, ctx)];
291
291
  }
292
- /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
293
- function buildWorksheetSubstream(sheet, ctx) {
292
+ /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, the sheet's own MsoDrawing/Obj records for images and embedded objects, EOF. */
293
+ function buildWorksheetSubstream(sheet, ctx, drawing) {
294
294
  for (const cell of sheet.cells) checkedCellPosition(cell);
295
295
  const writtenCells = sheet.cells.filter(require_written_cells.writesCellRecord);
296
296
  const cellsByRow = /* @__PURE__ */ new Map();
@@ -320,8 +320,9 @@ function buildWorksheetSubstream(sheet, ctx) {
320
320
  if (merges.length > 0) pieces.push(writeMergeCellsRecord(merges));
321
321
  const commentedCells = sheet.cells.filter((cell) => cell.comment !== void 0);
322
322
  if (commentedCells.length > 0) pieces.push(...require_workbook_comment_writer.writeSheetComments(commentedCells));
323
- pieces.push(...require_workbook_data_validation_write.writeSheetDataValidations(sheet));
323
+ pieces.push(...drawing.msoDrawingRecords, ...drawing.objRecords);
324
324
  pieces.push(...require_workbook_conditional_format_write.writeSheetConditionalFormats(sheet, ctx.icvOf));
325
+ pieces.push(...require_workbook_data_validation_write.writeSheetDataValidations(sheet));
325
326
  pieces.push(require_biff_record_writer.writeRecord(10, /* @__PURE__ */ new Uint8Array(0)));
326
327
  return require_biff_record_writer.concatRecords(...pieces);
327
328
  }
@@ -1,3 +1,4 @@
1
+ import { n as SheetDrawingWrite } from "../drawing-writer-Blm_NUSZ.cjs";
1
2
  import { Color, ContentSheet, ContentSheetCell } from "document-schema.js";
2
3
  //#region src/workbook/sheet-writer.d.ts
3
4
  interface SheetWriteContext {
@@ -8,7 +9,7 @@ interface SheetWriteContext {
8
9
  /** The shared string table index for a string cell's own text; every string a sheet writes must already be registered in the workbook-wide table before this is called. */
9
10
  sstIndexFor(text: string): number;
10
11
  }
11
- /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
12
- declare function buildWorksheetSubstream(sheet: ContentSheet, ctx: SheetWriteContext): Uint8Array<ArrayBuffer>;
12
+ /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, the sheet's own MsoDrawing/Obj records for images and embedded objects, EOF. */
13
+ declare function buildWorksheetSubstream(sheet: ContentSheet, ctx: SheetWriteContext, drawing: SheetDrawingWrite): Uint8Array<ArrayBuffer>;
13
14
  //#endregion
14
15
  export { SheetWriteContext, buildWorksheetSubstream };
@@ -1,3 +1,4 @@
1
+ import { n as SheetDrawingWrite } from "../drawing-writer-Blm_NUSZ.js";
1
2
  import { Color, ContentSheet, ContentSheetCell } from "document-schema.js";
2
3
  //#region src/workbook/sheet-writer.d.ts
3
4
  interface SheetWriteContext {
@@ -8,7 +9,7 @@ interface SheetWriteContext {
8
9
  /** The shared string table index for a string cell's own text; every string a sheet writes must already be registered in the workbook-wide table before this is called. */
9
10
  sstIndexFor(text: string): number;
10
11
  }
11
- /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
12
- declare function buildWorksheetSubstream(sheet: ContentSheet, ctx: SheetWriteContext): Uint8Array<ArrayBuffer>;
12
+ /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, the sheet's own MsoDrawing/Obj records for images and embedded objects, EOF. */
13
+ declare function buildWorksheetSubstream(sheet: ContentSheet, ctx: SheetWriteContext, drawing: SheetDrawingWrite): Uint8Array<ArrayBuffer>;
13
14
  //#endregion
14
15
  export { SheetWriteContext, buildWorksheetSubstream };
@@ -288,8 +288,8 @@ function writeFormulaRecords(cell, xfIndex) {
288
288
  function writeCellRecords(cell, xfIndex, ctx) {
289
289
  return cell.formula !== void 0 ? writeFormulaRecords(cell, xfIndex) : [writeCellValueRecord(cell, xfIndex, ctx)];
290
290
  }
291
- /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
292
- function buildWorksheetSubstream(sheet, ctx) {
291
+ /** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, the sheet's own MsoDrawing/Obj records for images and embedded objects, EOF. */
292
+ function buildWorksheetSubstream(sheet, ctx, drawing) {
293
293
  for (const cell of sheet.cells) checkedCellPosition(cell);
294
294
  const writtenCells = sheet.cells.filter(writesCellRecord);
295
295
  const cellsByRow = /* @__PURE__ */ new Map();
@@ -319,8 +319,9 @@ function buildWorksheetSubstream(sheet, ctx) {
319
319
  if (merges.length > 0) pieces.push(writeMergeCellsRecord(merges));
320
320
  const commentedCells = sheet.cells.filter((cell) => cell.comment !== void 0);
321
321
  if (commentedCells.length > 0) pieces.push(...writeSheetComments(commentedCells));
322
- pieces.push(...writeSheetDataValidations(sheet));
322
+ pieces.push(...drawing.msoDrawingRecords, ...drawing.objRecords);
323
323
  pieces.push(...writeSheetConditionalFormats(sheet, ctx.icvOf));
324
+ pieces.push(...writeSheetDataValidations(sheet));
324
325
  pieces.push(writeRecord(10, /* @__PURE__ */ new Uint8Array(0)));
325
326
  return concatRecords(...pieces);
326
327
  }