xls-codec 4.13.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.
- package/README.md +37 -19
- package/dist/biff/record-writer.cjs +18 -0
- package/dist/biff/record-writer.d.cts +7 -1
- package/dist/biff/record-writer.d.ts +7 -1
- package/dist/biff/record-writer.js +18 -1
- package/dist/container.cjs +9 -1
- package/dist/container.d.cts +2 -0
- package/dist/container.d.ts +2 -0
- package/dist/container.js +9 -1
- package/dist/content.cjs +6 -5
- package/dist/content.js +6 -5
- package/dist/drawing/escher-constants.cjs +6 -0
- package/dist/drawing/escher-constants.d.cts +5 -1
- package/dist/drawing/escher-constants.d.ts +5 -1
- package/dist/drawing/escher-constants.js +5 -1
- package/dist/drawing/escher-writer.cjs +107 -0
- package/dist/drawing/escher-writer.d.cts +32 -0
- package/dist/drawing/escher-writer.d.ts +32 -0
- package/dist/drawing/escher-writer.js +105 -0
- package/dist/drawing/md4.cjs +146 -0
- package/dist/drawing/md4.d.cts +7 -0
- package/dist/drawing/md4.d.ts +7 -0
- package/dist/drawing/md4.js +145 -0
- package/dist/drawing-writer-Blm_NUSZ.d.cts +19 -0
- package/dist/drawing-writer-Blm_NUSZ.d.ts +19 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/workbook/comments.cjs +26 -0
- package/dist/workbook/comments.d.cts +7 -1
- package/dist/workbook/comments.d.ts +7 -1
- package/dist/workbook/comments.js +26 -1
- package/dist/workbook/conditional-format-write.cjs +307 -18
- package/dist/workbook/conditional-format-write.js +307 -18
- package/dist/workbook/drawing-writer.cjs +253 -0
- package/dist/workbook/drawing-writer.d.cts +2 -0
- package/dist/workbook/drawing-writer.d.ts +2 -0
- package/dist/workbook/drawing-writer.js +252 -0
- package/dist/workbook/drawing.cjs +24 -3
- package/dist/workbook/drawing.d.cts +2 -0
- package/dist/workbook/drawing.d.ts +2 -0
- package/dist/workbook/drawing.js +25 -4
- package/dist/workbook/embedded-object.cjs +43 -0
- package/dist/workbook/embedded-object.d.cts +8 -0
- package/dist/workbook/embedded-object.d.ts +8 -0
- package/dist/workbook/embedded-object.js +41 -0
- package/dist/workbook/globals-writer.cjs +1 -0
- package/dist/workbook/globals-writer.d.cts +2 -0
- package/dist/workbook/globals-writer.d.ts +2 -0
- package/dist/workbook/globals-writer.js +2 -1
- package/dist/workbook/sheet-writer.cjs +4 -3
- package/dist/workbook/sheet-writer.d.cts +3 -2
- package/dist/workbook/sheet-writer.d.ts +3 -2
- package/dist/workbook/sheet-writer.js +4 -3
- package/dist/write.cjs +17 -7
- package/dist/write.js +17 -7
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../biff/record-types.js";
|
|
1
|
+
import { RECORD_CF12, RECORD_CONDFMT12 } from "../biff/record-types.js";
|
|
2
2
|
import { RecordBuilder } from "../biff/builder.js";
|
|
3
3
|
import { BiffWriteError } from "../biff/write-errors.js";
|
|
4
4
|
import { writeRecord } from "../biff/record-writer.js";
|
|
@@ -65,28 +65,317 @@ function boundingBoxOf(rule) {
|
|
|
65
65
|
endColumn
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
|
+
const CT_FORMULA = 2;
|
|
69
|
+
const CT_COLOR_SCALE = 3;
|
|
70
|
+
const CT_DATA_BAR = 4;
|
|
71
|
+
const CT_FILTER = 5;
|
|
72
|
+
const CT_ICON_SET = 6;
|
|
73
|
+
const ICF_TEMPLATE_COLOR_SCALE = 2;
|
|
74
|
+
const ICF_TEMPLATE_DATA_BAR = 3;
|
|
75
|
+
const ICF_TEMPLATE_ICON_SET = 4;
|
|
76
|
+
const ICF_TEMPLATE_FILTER = 5;
|
|
77
|
+
const ICF_TEMPLATE_UNIQUE_VALUES = 7;
|
|
78
|
+
const ICF_TEMPLATE_CONTAINS_TEXT = 8;
|
|
79
|
+
const ICF_TEMPLATE_CONTAINS_BLANKS = 9;
|
|
80
|
+
const ICF_TEMPLATE_CONTAINS_NO_BLANKS = 10;
|
|
81
|
+
const ICF_TEMPLATE_CONTAINS_ERRORS = 11;
|
|
82
|
+
const ICF_TEMPLATE_CONTAINS_NO_ERRORS = 12;
|
|
83
|
+
const ICF_TEMPLATE_ABOVE_AVERAGE = 25;
|
|
84
|
+
const ICF_TEMPLATE_BELOW_AVERAGE = 26;
|
|
85
|
+
const ICF_TEMPLATE_DUPLICATE_VALUES = 27;
|
|
86
|
+
const ICF_TEMPLATE_ABOVE_OR_EQUAL_AVERAGE = 29;
|
|
87
|
+
const ICF_TEMPLATE_BELOW_OR_EQUAL_AVERAGE = 30;
|
|
88
|
+
const TEMPLATE_PARAMS_SIZE = 16;
|
|
89
|
+
function cfvoTypeCodeOf(value) {
|
|
90
|
+
switch (value.type) {
|
|
91
|
+
case "num": return 1;
|
|
92
|
+
case "min": return 2;
|
|
93
|
+
case "max": return 3;
|
|
94
|
+
case "percent": return 4;
|
|
95
|
+
case "percentile": return 5;
|
|
96
|
+
case "formula": return 7;
|
|
97
|
+
default: return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function writeCfvo(value) {
|
|
101
|
+
const cfvoType = cfvoTypeCodeOf(value);
|
|
102
|
+
if (cfvoType === void 0) throw new BiffWriteError(`internal error: ContentSheetConditionalFormatValue carries type "${value.type}", which cfvoTypeCodeOf has no [MS-XLS] 2.5.40 cfvoType code for`);
|
|
103
|
+
if (value.type === "min" || value.type === "max") return new RecordBuilder().u8(cfvoType).u16(0).build();
|
|
104
|
+
if (value.value === void 0) throw new BiffWriteError(`a conditional-format threshold of type "${value.type}" carries no value; the shared schema states a value for every type but "min" and "max", and a CFVO has no spelling for a valueless threshold of any other kind`);
|
|
105
|
+
const rgce = compileFormulaText(value.value);
|
|
106
|
+
return new RecordBuilder().u8(cfvoType).u16(rgce.length).bytes(rgce).build();
|
|
107
|
+
}
|
|
108
|
+
function writeCfColor(color) {
|
|
109
|
+
return new RecordBuilder().u32(2).u8(Math.round(color.r * 255)).u8(Math.round(color.g * 255)).u8(Math.round(color.b * 255)).u8(0).f64(0).build();
|
|
110
|
+
}
|
|
111
|
+
function writeCfGradient(rule) {
|
|
112
|
+
const count = rule.stops.length;
|
|
113
|
+
const interpFractions = count === 3 ? [
|
|
114
|
+
0,
|
|
115
|
+
.5,
|
|
116
|
+
1
|
|
117
|
+
] : [0, 1];
|
|
118
|
+
const out = new RecordBuilder().u16(0).u8(0).u8(count).u8(count).u8(3);
|
|
119
|
+
rule.stops.forEach((stop, index) => {
|
|
120
|
+
out.bytes(writeCfvo(stop.value)).f64(interpFractions[index] ?? 1);
|
|
121
|
+
});
|
|
122
|
+
rule.stops.forEach((stop, index) => {
|
|
123
|
+
out.f64(interpFractions[index] ?? 1).bytes(writeCfColor(stop.color));
|
|
124
|
+
});
|
|
125
|
+
return out.build();
|
|
126
|
+
}
|
|
127
|
+
function writeCfDatabar(rule) {
|
|
128
|
+
return new RecordBuilder().u16(0).u8(0).u8(rule.showValue === false ? 0 : 2).u8(0).u8(100).bytes(writeCfColor(rule.color)).bytes(writeCfvo(rule.min)).bytes(writeCfvo(rule.max)).build();
|
|
129
|
+
}
|
|
130
|
+
function iconSetThresholdCount(iIconSet) {
|
|
131
|
+
if (iIconSet <= 7) return 3;
|
|
132
|
+
if (iIconSet <= 12) return 4;
|
|
133
|
+
return 5;
|
|
134
|
+
}
|
|
135
|
+
const ICON_SET_TYPE_NAMES = [
|
|
136
|
+
"3Arrows",
|
|
137
|
+
"3ArrowsGray",
|
|
138
|
+
"3Flags",
|
|
139
|
+
"3TrafficLights1",
|
|
140
|
+
"3TrafficLights2",
|
|
141
|
+
"3Signs",
|
|
142
|
+
"3Symbols",
|
|
143
|
+
"3Symbols2",
|
|
144
|
+
"4Arrows",
|
|
145
|
+
"4ArrowsGray",
|
|
146
|
+
"4RedToBlack",
|
|
147
|
+
"4Rating",
|
|
148
|
+
"4TrafficLights",
|
|
149
|
+
"5Arrows",
|
|
150
|
+
"5ArrowsGray",
|
|
151
|
+
"5Rating",
|
|
152
|
+
"5Quarters"
|
|
153
|
+
];
|
|
154
|
+
function writeCfMultistate(rule) {
|
|
155
|
+
const iIconSet = ICON_SET_TYPE_NAMES.indexOf(rule.iconSetType);
|
|
156
|
+
if (iIconSet < 0) throw new BiffWriteError(`xls-codec cannot write an icon-set rule naming the icon set "${rule.iconSetType}": [MS-XLS] 2.5.36's own iIconSet table names exactly the seventeen built-in sets, and a custom set has no BIFF8 byte to be written as`);
|
|
157
|
+
const expectedThresholds = iconSetThresholdCount(iIconSet);
|
|
158
|
+
if (rule.thresholds.length !== expectedThresholds) throw new BiffWriteError(`an icon-set rule naming the icon set "${rule.iconSetType}" carries ${rule.thresholds.length} thresholds where [MS-XLS] 2.5.36's own cStates table fixes the set at ${expectedThresholds}; the threshold count and the set cannot disagree in one CFMultistate`);
|
|
159
|
+
const out = new RecordBuilder().u16(0).u8(0).u8(expectedThresholds).u8(iIconSet).u8((rule.showValue === false ? 1 : 0) | (rule.reverse === true ? 4 : 0));
|
|
160
|
+
for (const threshold of rule.thresholds) {
|
|
161
|
+
out.bytes(writeCfvo(threshold)).u8(0);
|
|
162
|
+
out.u32(0);
|
|
163
|
+
}
|
|
164
|
+
return out.build();
|
|
165
|
+
}
|
|
166
|
+
function writeCfFilter(top10) {
|
|
167
|
+
const flags = top10 === void 0 ? 0 : (top10.bottom === true ? 0 : 1) | (top10.percent === true ? 2 : 0);
|
|
168
|
+
return new RecordBuilder().u16(4).u8(0).u8(flags).u16(top10?.rank ?? 0).build();
|
|
169
|
+
}
|
|
170
|
+
const TIME_PERIOD_TO_ICF_TEMPLATE = /* @__PURE__ */ new Map([
|
|
171
|
+
["today", 15],
|
|
172
|
+
["tomorrow", 16],
|
|
173
|
+
["yesterday", 17],
|
|
174
|
+
["last7Days", 18],
|
|
175
|
+
["lastMonth", 19],
|
|
176
|
+
["nextMonth", 20],
|
|
177
|
+
["thisWeek", 21],
|
|
178
|
+
["nextWeek", 22],
|
|
179
|
+
["lastWeek", 23],
|
|
180
|
+
["thisMonth", 24]
|
|
181
|
+
]);
|
|
182
|
+
const SIMPLE_KIND_TO_ICF_TEMPLATE = /* @__PURE__ */ new Map([
|
|
183
|
+
["containsBlanks", ICF_TEMPLATE_CONTAINS_BLANKS],
|
|
184
|
+
["notContainsBlanks", ICF_TEMPLATE_CONTAINS_NO_BLANKS],
|
|
185
|
+
["containsErrors", ICF_TEMPLATE_CONTAINS_ERRORS],
|
|
186
|
+
["notContainsErrors", ICF_TEMPLATE_CONTAINS_NO_ERRORS],
|
|
187
|
+
["uniqueValues", ICF_TEMPLATE_UNIQUE_VALUES],
|
|
188
|
+
["duplicateValues", ICF_TEMPLATE_DUPLICATE_VALUES]
|
|
189
|
+
]);
|
|
190
|
+
const CTP_BY_TEXT_TYPE = /* @__PURE__ */ new Map([
|
|
191
|
+
["containsText", 0],
|
|
192
|
+
["notContainsText", 1],
|
|
193
|
+
["beginsWith", 2],
|
|
194
|
+
["endsWith", 3]
|
|
195
|
+
]);
|
|
196
|
+
function textRuleFormula(rule) {
|
|
197
|
+
const anchor = rule.ranges[0];
|
|
198
|
+
if (anchor === void 0) throw new BiffWriteError("internal error: textRuleFormula was called for a rule whose ranges were never validated");
|
|
199
|
+
const cell = relativeCellRef(anchor.startRow, anchor.startColumn);
|
|
200
|
+
const text = `"${rule.text.replaceAll("\"", "\"\"")}"`;
|
|
201
|
+
switch (rule.type) {
|
|
202
|
+
case "containsText": return `NOT(ISERROR(SEARCH(${text},${cell})))`;
|
|
203
|
+
case "notContainsText": return `ISERROR(SEARCH(${text},${cell}))`;
|
|
204
|
+
case "beginsWith": return `LEFT(${cell},LEN(${text}))=${text}`;
|
|
205
|
+
case "endsWith": return `RIGHT(${cell},LEN(${text}))=${text}`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function relativeCellRef(row, column) {
|
|
209
|
+
let letters = "";
|
|
210
|
+
let index = column;
|
|
211
|
+
do {
|
|
212
|
+
letters = String.fromCharCode(65 + index % 26) + letters;
|
|
213
|
+
index = Math.floor(index / 26) - 1;
|
|
214
|
+
} while (index >= 0);
|
|
215
|
+
return `${letters}${row + 1}`;
|
|
216
|
+
}
|
|
217
|
+
function writeCf12Record(rule, ipriority, icvOf) {
|
|
218
|
+
let ct;
|
|
219
|
+
let icfTemplate;
|
|
220
|
+
let templateParams;
|
|
221
|
+
let rgbCt;
|
|
222
|
+
let rgce1;
|
|
223
|
+
let dxf;
|
|
224
|
+
switch (rule.type) {
|
|
225
|
+
case "colorScale":
|
|
226
|
+
ct = CT_COLOR_SCALE;
|
|
227
|
+
icfTemplate = ICF_TEMPLATE_COLOR_SCALE;
|
|
228
|
+
templateParams = new Uint8Array(TEMPLATE_PARAMS_SIZE);
|
|
229
|
+
rgbCt = writeCfGradient(rule);
|
|
230
|
+
dxf = /* @__PURE__ */ new Uint8Array(0);
|
|
231
|
+
break;
|
|
232
|
+
case "dataBar":
|
|
233
|
+
ct = CT_DATA_BAR;
|
|
234
|
+
icfTemplate = ICF_TEMPLATE_DATA_BAR;
|
|
235
|
+
templateParams = new Uint8Array(TEMPLATE_PARAMS_SIZE);
|
|
236
|
+
rgbCt = writeCfDatabar(rule);
|
|
237
|
+
dxf = /* @__PURE__ */ new Uint8Array(0);
|
|
238
|
+
break;
|
|
239
|
+
case "iconSet":
|
|
240
|
+
ct = CT_ICON_SET;
|
|
241
|
+
icfTemplate = ICF_TEMPLATE_ICON_SET;
|
|
242
|
+
templateParams = new Uint8Array(TEMPLATE_PARAMS_SIZE);
|
|
243
|
+
rgbCt = writeCfMultistate(rule);
|
|
244
|
+
dxf = /* @__PURE__ */ new Uint8Array(0);
|
|
245
|
+
break;
|
|
246
|
+
case "top10":
|
|
247
|
+
ct = CT_FILTER;
|
|
248
|
+
icfTemplate = ICF_TEMPLATE_FILTER;
|
|
249
|
+
templateParams = new RecordBuilder().u8((rule.bottom === true ? 0 : 1) | (rule.percent === true ? 2 : 0)).u16(rule.rank).bytes(/* @__PURE__ */ new Uint8Array(13)).build();
|
|
250
|
+
rgbCt = writeCfFilter(rule);
|
|
251
|
+
dxf = writeDxfn(rule.style, icvOf);
|
|
252
|
+
break;
|
|
253
|
+
case "aboveAverage": {
|
|
254
|
+
ct = CT_FILTER;
|
|
255
|
+
icfTemplate = !(rule.aboveAverage ?? true) ? rule.equalAverage === true ? ICF_TEMPLATE_BELOW_OR_EQUAL_AVERAGE : ICF_TEMPLATE_BELOW_AVERAGE : rule.equalAverage === true ? ICF_TEMPLATE_ABOVE_OR_EQUAL_AVERAGE : ICF_TEMPLATE_ABOVE_AVERAGE;
|
|
256
|
+
const stdDev = rule.stdDev ?? 0;
|
|
257
|
+
if (stdDev > 2) throw new BiffWriteError(`an aboveAverage rule declaring ${stdDev} standard deviations cannot be written: [MS-XLS] 2.5.23's own iParam table admits only 0, 1, or 2`);
|
|
258
|
+
templateParams = new RecordBuilder().u16(stdDev).bytes(/* @__PURE__ */ new Uint8Array(14)).build();
|
|
259
|
+
rgbCt = writeCfFilter(void 0);
|
|
260
|
+
dxf = writeDxfn(rule.style, icvOf);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case "timePeriod": {
|
|
264
|
+
ct = CT_FILTER;
|
|
265
|
+
const template = TIME_PERIOD_TO_ICF_TEMPLATE.get(rule.timePeriod);
|
|
266
|
+
if (template === void 0) throw new BiffWriteError(`a timePeriod rule naming "${rule.timePeriod}" cannot be written: that member exists only in LibreOffice's calcext extension vocabulary ([MS-XLS] 2.4.43's own icfTemplate table has no year-scoped period), so no BIFF8 rule names it`);
|
|
267
|
+
icfTemplate = template;
|
|
268
|
+
templateParams = new RecordBuilder().u16(template).bytes(/* @__PURE__ */ new Uint8Array(14)).build();
|
|
269
|
+
rgbCt = writeCfFilter(void 0);
|
|
270
|
+
dxf = writeDxfn(rule.style, icvOf);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "containsBlanks":
|
|
274
|
+
case "notContainsBlanks":
|
|
275
|
+
case "containsErrors":
|
|
276
|
+
case "notContainsErrors":
|
|
277
|
+
case "uniqueValues":
|
|
278
|
+
case "duplicateValues": {
|
|
279
|
+
ct = CT_FILTER;
|
|
280
|
+
const template = SIMPLE_KIND_TO_ICF_TEMPLATE.get(rule.type);
|
|
281
|
+
if (template === void 0) throw new BiffWriteError(`internal error: SIMPLE_KIND_TO_ICF_TEMPLATE has no icfTemplate for rule type "${rule.type}"`);
|
|
282
|
+
icfTemplate = template;
|
|
283
|
+
templateParams = new Uint8Array(TEMPLATE_PARAMS_SIZE);
|
|
284
|
+
rgbCt = writeCfFilter(void 0);
|
|
285
|
+
dxf = writeDxfn(rule.style, icvOf);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case "containsText":
|
|
289
|
+
case "notContainsText":
|
|
290
|
+
case "beginsWith":
|
|
291
|
+
case "endsWith": {
|
|
292
|
+
ct = CT_FORMULA;
|
|
293
|
+
icfTemplate = ICF_TEMPLATE_CONTAINS_TEXT;
|
|
294
|
+
const ctp = CTP_BY_TEXT_TYPE.get(rule.type);
|
|
295
|
+
if (ctp === void 0) throw new BiffWriteError(`internal error: CTP_BY_TEXT_TYPE has no ctp for rule type "${rule.type}"`);
|
|
296
|
+
templateParams = new RecordBuilder().u16(ctp).bytes(/* @__PURE__ */ new Uint8Array(14)).build();
|
|
297
|
+
rgce1 = compileFormulaText(textRuleFormula(rule));
|
|
298
|
+
dxf = writeDxfn(rule.style, icvOf);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
default: throw new BiffWriteError("internal error: writeCf12Record was called for a rule type its own dispatch never names");
|
|
302
|
+
}
|
|
303
|
+
const writer = new RecordBuilder().u16(RECORD_CF12).u16(0).bytes(/* @__PURE__ */ new Uint8Array(8)).u8(ct).u8(0).u16(rgce1?.length ?? 0).u16(0).u32(dxf.length).bytes(dxf);
|
|
304
|
+
if (rgce1 !== void 0) writer.bytes(rgce1);
|
|
305
|
+
writer.u16(0).u8(rule.stopIfTrue === true ? 2 : 0).u16(ipriority).u16(icfTemplate).u8(TEMPLATE_PARAMS_SIZE).bytes(templateParams);
|
|
306
|
+
if (rgbCt !== void 0) writer.bytes(rgbCt);
|
|
307
|
+
return writeRecord(RECORD_CF12, writer.build());
|
|
308
|
+
}
|
|
309
|
+
function assignPriorities(rules) {
|
|
310
|
+
const used = /* @__PURE__ */ new Set();
|
|
311
|
+
for (const rule of rules) {
|
|
312
|
+
if (rule.priority === void 0) continue;
|
|
313
|
+
if (used.has(rule.priority)) throw new BiffWriteError(`two conditional-format rules declare priority ${rule.priority}; [MS-XLS] 2.4.43 requires ipriority to be unique across a sheet's CF12 records, so a document repeating one is contradictory rather than orderable`);
|
|
314
|
+
used.add(rule.priority);
|
|
315
|
+
}
|
|
316
|
+
return rules.map((rule) => {
|
|
317
|
+
if (rule.priority !== void 0) return rule.priority;
|
|
318
|
+
let candidate = 1;
|
|
319
|
+
while (used.has(candidate)) candidate += 1;
|
|
320
|
+
used.add(candidate);
|
|
321
|
+
return candidate;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
function writeCondFmt12Record(rule, nID) {
|
|
325
|
+
const box = boundingBoxOf(rule);
|
|
326
|
+
const header = new RecordBuilder().u16(RECORD_CONDFMT12).u16(1).u16(box.startRow).u16(box.endRow).u16(box.startColumn).u16(box.endColumn).u16(1).u16(nID << 1).u16(box.startRow).u16(box.endRow).u16(box.startColumn).u16(box.endColumn).u16(rule.ranges.length);
|
|
327
|
+
for (const range of rule.ranges) {
|
|
328
|
+
header.u16(range.startRow);
|
|
329
|
+
header.u16(range.endRow);
|
|
330
|
+
header.u16(range.startColumn);
|
|
331
|
+
header.u16(range.endColumn);
|
|
332
|
+
}
|
|
333
|
+
return writeRecord(RECORD_CONDFMT12, header.build());
|
|
334
|
+
}
|
|
335
|
+
function validateRuleGrid(rule) {
|
|
336
|
+
if (rule.ranges.length === 0) throw new BiffWriteError("a conditional-format rule carrying no range states nothing; the schema requires at least one");
|
|
337
|
+
for (const range of rule.ranges) if (range.startRow > 65535 || range.endRow > 65535 || range.startColumn > 255 || range.endColumn > 255) throw new BiffWriteError(`a conditional-format range (rows ${range.startRow}-${range.endRow}, columns ${range.startColumn}-${range.endColumn}) is outside BIFF8's own grid; a .xls workbook cannot address it`);
|
|
338
|
+
}
|
|
339
|
+
function validateStopIfTrue(rule) {
|
|
340
|
+
if (rule.stopIfTrue === true && (rule.type === "colorScale" || rule.type === "dataBar" || rule.type === "iconSet")) throw new BiffWriteError(`a conditional-format rule of type "${rule.type}" declaring stopIfTrue cannot be written: [MS-XLS] 2.4.43 pins CF12's own fStopIfTrue bit to zero for the visual-scale rule types`);
|
|
341
|
+
}
|
|
68
342
|
function writeSheetConditionalFormats(sheet, icvOf) {
|
|
69
343
|
const rules = sheet.conditionalFormats ?? [];
|
|
70
344
|
if (rules.length === 0) return [];
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
for (const range of rule.ranges) {
|
|
81
|
-
header.u16(range.startRow);
|
|
82
|
-
header.u16(range.endRow);
|
|
83
|
-
header.u16(range.startColumn);
|
|
84
|
-
header.u16(range.endColumn);
|
|
345
|
+
const basePieces = [];
|
|
346
|
+
const cf12Pieces = [];
|
|
347
|
+
if (rules.length > 32767) throw new BiffWriteError(`this sheet's ${rules.length} conditional-format rules exceed CondFmt's own 15-bit nID field`);
|
|
348
|
+
const cf12Rules = [];
|
|
349
|
+
rules.forEach((rule, index) => {
|
|
350
|
+
validateRuleGrid(rule);
|
|
351
|
+
if (rule.type === "cellIs") {
|
|
352
|
+
basePieces.push(writeCondFmtRecord(rule, index + 1), writeCfRecord(rule, icvOf));
|
|
353
|
+
return;
|
|
85
354
|
}
|
|
86
|
-
|
|
87
|
-
|
|
355
|
+
validateStopIfTrue(rule);
|
|
356
|
+
cf12Rules.push({
|
|
357
|
+
rule,
|
|
358
|
+
nID: index + 1
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
const priorities = assignPriorities(cf12Rules.map(({ rule }) => rule));
|
|
362
|
+
cf12Rules.forEach(({ rule, nID }, index) => {
|
|
363
|
+
const ipriority = priorities[index];
|
|
364
|
+
if (ipriority === void 0) throw new BiffWriteError("internal error: assignPriorities returned fewer priorities than there are CF12 rules");
|
|
365
|
+
cf12Pieces.push(writeCondFmt12Record(rule, nID), writeCf12Record(rule, ipriority, icvOf));
|
|
366
|
+
});
|
|
367
|
+
return [...basePieces, ...cf12Pieces];
|
|
368
|
+
}
|
|
369
|
+
function writeCondFmtRecord(rule, nID) {
|
|
370
|
+
const box = boundingBoxOf(rule);
|
|
371
|
+
const header = new RecordBuilder().u16(1).u16(nID << 1).u16(box.startRow).u16(box.endRow).u16(box.startColumn).u16(box.endColumn).u16(rule.ranges.length);
|
|
372
|
+
for (const range of rule.ranges) {
|
|
373
|
+
header.u16(range.startRow);
|
|
374
|
+
header.u16(range.endRow);
|
|
375
|
+
header.u16(range.startColumn);
|
|
376
|
+
header.u16(range.endColumn);
|
|
88
377
|
}
|
|
89
|
-
return
|
|
378
|
+
return writeRecord(432, header.build());
|
|
90
379
|
}
|
|
91
380
|
//#endregion
|
|
92
381
|
export { writeSheetConditionalFormats };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../biff/record-types.cjs");
|
|
3
|
+
const require_units = require("../units.cjs");
|
|
4
|
+
const require_biff_builder = require("../biff/builder.cjs");
|
|
5
|
+
const require_biff_write_errors = require("../biff/write-errors.cjs");
|
|
6
|
+
const require_biff_record_writer = require("../biff/record-writer.cjs");
|
|
7
|
+
const require_biff_string_writer = require("../biff/string-writer.cjs");
|
|
8
|
+
const require_workbook_embedded_object = require("./embedded-object.cjs");
|
|
9
|
+
const require_drawing_escher_writer = require("../drawing/escher-writer.cjs");
|
|
10
|
+
//#region src/workbook/drawing-writer.ts
|
|
11
|
+
/** 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. */
|
|
12
|
+
var WriterGridGeometry = class {
|
|
13
|
+
columnWidths = /* @__PURE__ */ new Map();
|
|
14
|
+
rowHeights = /* @__PURE__ */ new Map();
|
|
15
|
+
defaultColumnWidthPt = require_units.columnWidthToPoints(require_units.DEFAULT_COLUMN_WIDTH_CHARS * 256);
|
|
16
|
+
constructor(sheet) {
|
|
17
|
+
for (const column of sheet.columns) if (column.widthPt !== void 0) this.columnWidths.set(column.index, column.widthPt);
|
|
18
|
+
for (const row of sheet.rows) if (row.heightPt !== void 0) this.rowHeights.set(row.index, row.heightPt);
|
|
19
|
+
}
|
|
20
|
+
columnWidthPt(index) {
|
|
21
|
+
return this.columnWidths.get(index) ?? this.defaultColumnWidthPt;
|
|
22
|
+
}
|
|
23
|
+
rowHeightPt(index) {
|
|
24
|
+
return this.rowHeights.get(index) ?? 15;
|
|
25
|
+
}
|
|
26
|
+
/** 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. */
|
|
27
|
+
xPt(column) {
|
|
28
|
+
let x = 0;
|
|
29
|
+
for (let index = 0; index < column; index += 1) x += this.columnWidthPt(index);
|
|
30
|
+
return x;
|
|
31
|
+
}
|
|
32
|
+
yPt(row) {
|
|
33
|
+
let y = 0;
|
|
34
|
+
for (let index = 0; index < row; index += 1) y += this.rowHeightPt(index);
|
|
35
|
+
return y;
|
|
36
|
+
}
|
|
37
|
+
/** 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). */
|
|
38
|
+
locateX(x) {
|
|
39
|
+
let left = 0;
|
|
40
|
+
for (let column = 0; column < 255; column += 1) {
|
|
41
|
+
const width = this.columnWidthPt(column);
|
|
42
|
+
if (x < left + width) return {
|
|
43
|
+
column,
|
|
44
|
+
fraction: Math.min(1023, Math.round((x - left) / width * 1024))
|
|
45
|
+
};
|
|
46
|
+
left += width;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
column: 255,
|
|
50
|
+
fraction: 1023
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** The row-axis counterpart: a row plus a 1/256ths-of-that-row fraction, clamped to the grid's own last row. */
|
|
54
|
+
locateY(y) {
|
|
55
|
+
let top = 0;
|
|
56
|
+
for (let row = 0; row < 65535; row += 1) {
|
|
57
|
+
const height = this.rowHeightPt(row);
|
|
58
|
+
if (y < top + height) return {
|
|
59
|
+
row,
|
|
60
|
+
fraction: Math.min(255, Math.round((y - top) / height * 256))
|
|
61
|
+
};
|
|
62
|
+
top += height;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
row: 65535,
|
|
66
|
+
fraction: 255
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function placementOfImage(image, geometry) {
|
|
71
|
+
if (image.anchorRow > 65535 || image.anchorColumn > 255) throw new require_biff_write_errors.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`);
|
|
72
|
+
return {
|
|
73
|
+
startXPt: geometry.xPt(image.anchorColumn) + image.offsetXPt,
|
|
74
|
+
startYPt: geometry.yPt(image.anchorRow) + image.offsetYPt,
|
|
75
|
+
widthPt: image.widthPt,
|
|
76
|
+
heightPt: image.heightPt
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function placementOfEmbedded(embedded, geometry) {
|
|
80
|
+
return {
|
|
81
|
+
startXPt: embedded.anchorColumn !== void 0 && embedded.offsetXPt !== void 0 ? geometry.xPt(embedded.anchorColumn) + embedded.offsetXPt : embedded.frame.xPt,
|
|
82
|
+
startYPt: embedded.anchorRow !== void 0 && embedded.offsetYPt !== void 0 ? geometry.yPt(embedded.anchorRow) + embedded.offsetYPt : embedded.frame.yPt,
|
|
83
|
+
widthPt: embedded.frame.widthPt,
|
|
84
|
+
heightPt: embedded.frame.heightPt
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** 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. */
|
|
88
|
+
function anchorOf(placement, geometry) {
|
|
89
|
+
const start = geometry.locateX(placement.startXPt);
|
|
90
|
+
const end = geometry.locateX(placement.startXPt + placement.widthPt);
|
|
91
|
+
const top = geometry.locateY(placement.startYPt);
|
|
92
|
+
const bottom = geometry.locateY(placement.startYPt + placement.heightPt);
|
|
93
|
+
return {
|
|
94
|
+
colL: start.column,
|
|
95
|
+
dxL: start.fraction,
|
|
96
|
+
rwT: top.row,
|
|
97
|
+
dyT: top.fraction,
|
|
98
|
+
colR: end.column,
|
|
99
|
+
dxR: end.fraction,
|
|
100
|
+
rwB: bottom.row,
|
|
101
|
+
dyB: bottom.fraction
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** [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"). */
|
|
105
|
+
const OBJECT_TYPE_PICTURE = 8;
|
|
106
|
+
/** 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. */
|
|
107
|
+
function writeFtCmo(ot, id) {
|
|
108
|
+
return new require_biff_builder.RecordBuilder().u16(21).u16(18).u16(ot).u16(id).u16(0).u32(0).u32(0).u32(0).build();
|
|
109
|
+
}
|
|
110
|
+
/** 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. */
|
|
111
|
+
function writeFtCf() {
|
|
112
|
+
return new require_biff_builder.RecordBuilder().u16(7).u16(2).u16(65535).build();
|
|
113
|
+
}
|
|
114
|
+
/** 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. */
|
|
115
|
+
function writeFtPioGrbit(autoPict) {
|
|
116
|
+
return new require_biff_builder.RecordBuilder().u16(8).u16(2).u16(autoPict ? 1 : 0).build();
|
|
117
|
+
}
|
|
118
|
+
/** 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. */
|
|
119
|
+
const PTG_TBL = 2;
|
|
120
|
+
/** 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. */
|
|
121
|
+
const EMBED_CLASS_NAME = "Package";
|
|
122
|
+
/** 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. */
|
|
123
|
+
function writeFtPictFmla(storageId) {
|
|
124
|
+
const formula = new require_biff_builder.RecordBuilder().u16(5).u32(0).u8(PTG_TBL).bytes(/* @__PURE__ */ new Uint8Array(4)).build();
|
|
125
|
+
const className = require_biff_string_writer.writeXLUnicodeStringNoCch(EMBED_CLASS_NAME);
|
|
126
|
+
const embedInfo = new require_biff_builder.RecordBuilder().u8(3).u8(className.length - 1).u8(0).bytes(className).build();
|
|
127
|
+
const fmlaBytes = new require_biff_builder.RecordBuilder().bytes(formula).bytes(embedInfo).build();
|
|
128
|
+
const data = new require_biff_builder.RecordBuilder().u16(fmlaBytes.length).bytes(fmlaBytes).u32(storageId).build();
|
|
129
|
+
return new require_biff_builder.RecordBuilder().u16(9).u16(data.length).bytes(data).build();
|
|
130
|
+
}
|
|
131
|
+
/** 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. */
|
|
132
|
+
const OBJ_RESERVED_END = /* @__PURE__ */ new Uint8Array(4);
|
|
133
|
+
/** 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. */
|
|
134
|
+
function writePictureObjRecord(objectId) {
|
|
135
|
+
return require_biff_record_writer.writeRecord(93, new require_biff_builder.RecordBuilder().bytes(writeFtCmo(OBJECT_TYPE_PICTURE, objectId)).bytes(writeFtCf()).bytes(writeFtPioGrbit(true)).bytes(OBJ_RESERVED_END).build());
|
|
136
|
+
}
|
|
137
|
+
/** 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. */
|
|
138
|
+
function writeEmbeddedObjRecord(objectId, storageId) {
|
|
139
|
+
return require_biff_record_writer.writeRecord(93, new require_biff_builder.RecordBuilder().bytes(writeFtCmo(OBJECT_TYPE_PICTURE, objectId)).bytes(writeFtCf()).bytes(writeFtPioGrbit(false)).bytes(writeFtPictFmla(storageId)).bytes(OBJ_RESERVED_END).build());
|
|
140
|
+
}
|
|
141
|
+
/** 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. */
|
|
142
|
+
function bytesFromBase64(base64) {
|
|
143
|
+
const values = (/* @__PURE__ */ new Int8Array(256)).fill(-1);
|
|
144
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("").forEach((char, index) => {
|
|
145
|
+
values[char.charCodeAt(0)] = index;
|
|
146
|
+
});
|
|
147
|
+
const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
|
|
148
|
+
const out = new Uint8Array(base64.length / 4 * 3 - padding);
|
|
149
|
+
let buffer = 0;
|
|
150
|
+
let bits = 0;
|
|
151
|
+
let outIndex = 0;
|
|
152
|
+
for (const char of base64) {
|
|
153
|
+
const value = values[char.charCodeAt(0)];
|
|
154
|
+
if (value === void 0 || value < 0) continue;
|
|
155
|
+
buffer = buffer << 6 | value;
|
|
156
|
+
bits += 6;
|
|
157
|
+
if (bits >= 8) {
|
|
158
|
+
bits -= 8;
|
|
159
|
+
out[outIndex] = buffer >> bits & 255;
|
|
160
|
+
outIndex += 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
function buildDrawingWritePlan(sheets) {
|
|
166
|
+
const blips = [];
|
|
167
|
+
const blipIndexByBase64 = /* @__PURE__ */ new Map();
|
|
168
|
+
const resolveBlip = (image) => {
|
|
169
|
+
if (image.format !== "png" && image.format !== "jpeg") throw new require_biff_write_errors.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`);
|
|
170
|
+
const existing = blipIndexByBase64.get(image.base64);
|
|
171
|
+
if (existing !== void 0) {
|
|
172
|
+
const blip = blips[existing - 1];
|
|
173
|
+
if (blip === void 0) throw new require_biff_write_errors.BiffWriteError("internal error: a blip index resolved that the workbook-wide image scan never assigned");
|
|
174
|
+
blip.referenceCount += 1;
|
|
175
|
+
return existing;
|
|
176
|
+
}
|
|
177
|
+
const index = blips.length + 1;
|
|
178
|
+
blipIndexByBase64.set(image.base64, index);
|
|
179
|
+
blips.push({
|
|
180
|
+
format: image.format,
|
|
181
|
+
fileBytes: bytesFromBase64(image.base64),
|
|
182
|
+
referenceCount: 1
|
|
183
|
+
});
|
|
184
|
+
return index;
|
|
185
|
+
};
|
|
186
|
+
let nextStorageId = 1;
|
|
187
|
+
const storageIdOf = () => {
|
|
188
|
+
const id = nextStorageId;
|
|
189
|
+
nextStorageId += 1;
|
|
190
|
+
return id;
|
|
191
|
+
};
|
|
192
|
+
let nextSpid = 1024;
|
|
193
|
+
let nextDrawingId = 1;
|
|
194
|
+
const drawingBlocks = [];
|
|
195
|
+
const sheetDrawings = [];
|
|
196
|
+
const embeddingStreams = [];
|
|
197
|
+
for (const sheet of sheets) {
|
|
198
|
+
const geometry = new WriterGridGeometry(sheet);
|
|
199
|
+
const entries = [];
|
|
200
|
+
const objRecords = [];
|
|
201
|
+
let nextObjectId = sheet.cells.filter((cell) => cell.comment !== void 0).length + 1;
|
|
202
|
+
for (const image of sheet.images) {
|
|
203
|
+
entries.push({
|
|
204
|
+
anchor: anchorOf(placementOfImage(image, geometry), geometry),
|
|
205
|
+
blipIndex: resolveBlip(image),
|
|
206
|
+
oleShape: false
|
|
207
|
+
});
|
|
208
|
+
objRecords.push(writePictureObjRecord(nextObjectId));
|
|
209
|
+
nextObjectId += 1;
|
|
210
|
+
}
|
|
211
|
+
for (const embedded of sheet.embeddedObjects ?? []) {
|
|
212
|
+
if (embedded.objectKind === "chart") throw new require_biff_write_errors.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");
|
|
213
|
+
const storageId = storageIdOf();
|
|
214
|
+
entries.push({
|
|
215
|
+
anchor: anchorOf(placementOfEmbedded(embedded, geometry), geometry),
|
|
216
|
+
blipIndex: void 0,
|
|
217
|
+
oleShape: true
|
|
218
|
+
});
|
|
219
|
+
objRecords.push(writeEmbeddedObjRecord(nextObjectId, storageId));
|
|
220
|
+
nextObjectId += 1;
|
|
221
|
+
embeddingStreams.push({
|
|
222
|
+
path: `MBD${storageId.toString(16).toUpperCase().padStart(8, "0")}/Package`,
|
|
223
|
+
bytes: require_workbook_embedded_object.writeEmbeddedObjectPackage(embedded)
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (entries.length === 0) {
|
|
227
|
+
sheetDrawings.push({
|
|
228
|
+
msoDrawingRecords: [],
|
|
229
|
+
objRecords: []
|
|
230
|
+
});
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const escherBytes = require_drawing_escher_writer.writeSheetDrawingBytes(nextDrawingId, nextSpid, entries);
|
|
234
|
+
drawingBlocks.push({
|
|
235
|
+
drawingId: nextDrawingId,
|
|
236
|
+
lastSpid: nextSpid + entries.length,
|
|
237
|
+
shapeCount: entries.length + 1
|
|
238
|
+
});
|
|
239
|
+
nextDrawingId += 1;
|
|
240
|
+
nextSpid += entries.length + 1;
|
|
241
|
+
sheetDrawings.push({
|
|
242
|
+
msoDrawingRecords: require_biff_record_writer.writeRecordChain(236, escherBytes),
|
|
243
|
+
objRecords
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
drawingGroupBytes: drawingBlocks.length > 0 ? require_drawing_escher_writer.writeDrawingGroupBytes(blips, drawingBlocks) : void 0,
|
|
248
|
+
sheetDrawings,
|
|
249
|
+
embeddingStreams
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
exports.buildDrawingWritePlan = buildDrawingWritePlan;
|