doc-codec 2.5.0 → 2.7.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 (42) hide show
  1. package/README.md +38 -29
  2. package/dist/base64.cjs +48 -0
  3. package/dist/base64.d.cts +5 -0
  4. package/dist/base64.d.ts +5 -0
  5. package/dist/base64.js +46 -0
  6. package/dist/data-stream-BLCj4-V1.d.cts +11 -0
  7. package/dist/data-stream-BLCj4-V1.d.ts +11 -0
  8. package/dist/data-stream.cjs +25 -0
  9. package/dist/data-stream.d.cts +2 -0
  10. package/dist/data-stream.d.ts +2 -0
  11. package/dist/data-stream.js +24 -0
  12. package/dist/encryption.cjs +44 -10
  13. package/dist/encryption.d.cts +2 -2
  14. package/dist/encryption.d.ts +2 -2
  15. package/dist/encryption.js +45 -11
  16. package/dist/index.cjs +5 -0
  17. package/dist/index.d.cts +3 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +3 -1
  20. package/dist/pictures-write.cjs +85 -0
  21. package/dist/pictures-write.d.cts +12 -0
  22. package/dist/pictures-write.d.ts +12 -0
  23. package/dist/pictures-write.js +84 -0
  24. package/dist/pictures.cjs +3 -16
  25. package/dist/pictures.js +3 -16
  26. package/dist/prop/sep-write.cjs +16 -9
  27. package/dist/prop/sep-write.d.cts +2 -2
  28. package/dist/prop/sep-write.d.ts +2 -2
  29. package/dist/prop/sep-write.js +16 -9
  30. package/dist/read.cjs +1 -2
  31. package/dist/read.js +2 -3
  32. package/dist/table/write.cjs +28 -3
  33. package/dist/table/write.d.cts +2 -2
  34. package/dist/table/write.d.ts +2 -2
  35. package/dist/table/write.js +28 -3
  36. package/dist/{write-SN_aIwyF.d.ts → write-CMB3qF4i.d.cts} +9 -3
  37. package/dist/{write-SN_aIwyF.d.cts → write-t4rFgnA-.d.ts} +9 -3
  38. package/dist/write.cjs +65 -17
  39. package/dist/write.d.cts +1 -1
  40. package/dist/write.d.ts +1 -1
  41. package/dist/write.js +66 -18
  42. package/package.json +2 -2
@@ -1,7 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("../errors.cjs");
3
+ require("../data-stream.cjs");
3
4
  require("../text/special.cjs");
4
5
  const require_prop_fkp_write = require("../prop/fkp-write.cjs");
6
+ const require_pictures_write = require("../pictures-write.cjs");
5
7
  const require_table_tap_write = require("./tap-write.cjs");
6
8
  //#region src/table/write.ts
7
9
  /** sprmPFInTable (0x2416): a Bool8, "MUST be 1 any time the table depth is greater than zero". */
@@ -9,6 +11,12 @@ const SPRM_P_F_IN_TABLE = 9238;
9
11
  /** sprmPFTtp (0x2417): a Bool8 marking a cell mark as the row's own Table Terminating Paragraph mark. */
10
12
  const SPRM_P_F_TTP = 9239;
11
13
  const TWIPS_PER_POINT = 20;
14
+ function plainRuns(runs) {
15
+ return runs.map((run) => ({
16
+ run,
17
+ extraGrpprl: []
18
+ }));
19
+ }
12
20
  function pushSprm(bytes, opcode, operand) {
13
21
  bytes.push(opcode & 255, opcode >> 8 & 255, ...operand);
14
22
  }
@@ -33,7 +41,7 @@ function cellParagraphs(blocks) {
33
41
  return blocks.map((block, index) => {
34
42
  if (block.kind !== "paragraph") throw new require_errors.DocUnsupportedError(`doc-codec's writer does not support a '${block.kind}' block inside a table cell (only paragraphs are; nested tables are a separately-tracked gap -- see the README's scope note)`);
35
43
  return {
36
- runs: block.runs,
44
+ runs: plainRuns(block.runs),
37
45
  properties: block,
38
46
  extraGrpprl: inTableGrpprl(),
39
47
  terminator: index === blocks.length - 1 ? 7 : 13
@@ -189,12 +197,25 @@ function flattenTable(table, blockIndex, onWarning) {
189
197
  });
190
198
  return output;
191
199
  }
192
- function flattenSectionBlocks(blocks, onWarning) {
200
+ function imageParagraph(image, dataStream) {
201
+ const { data, buildGrpprl } = require_pictures_write.buildInlinePicture(image);
202
+ const offset = dataStream.append(data);
203
+ return {
204
+ runs: [{
205
+ run: { text: String.fromCharCode(1) },
206
+ extraGrpprl: buildGrpprl(offset)
207
+ }],
208
+ properties: {},
209
+ extraGrpprl: [],
210
+ terminator: 13
211
+ };
212
+ }
213
+ function flattenSectionBlocks(blocks, dataStream, onWarning) {
193
214
  const output = [];
194
215
  blocks.forEach((block, blockIndex) => {
195
216
  if (block.kind === "paragraph") {
196
217
  output.push({
197
- runs: block.runs,
218
+ runs: plainRuns(block.runs),
198
219
  properties: block,
199
220
  extraGrpprl: [],
200
221
  terminator: 13
@@ -205,6 +226,10 @@ function flattenSectionBlocks(blocks, onWarning) {
205
226
  output.push(...flattenTable(block, blockIndex, onWarning));
206
227
  return;
207
228
  }
229
+ if (block.kind === "image") {
230
+ output.push(imageParagraph(block, dataStream));
231
+ return;
232
+ }
208
233
  throw new require_errors.DocUnsupportedError(`doc-codec's writer does not yet support '${block.kind}' blocks (see README's scope note)`);
209
234
  });
210
235
  return output;
@@ -1,2 +1,2 @@
1
- import { n as WriteWarning, r as flattenSectionBlocks, t as WriteParagraph } from "../write-SN_aIwyF.cjs";
2
- export { WriteParagraph, WriteWarning, flattenSectionBlocks };
1
+ import { i as flattenSectionBlocks, n as WriteRun, r as WriteWarning, t as WriteParagraph } from "../write-CMB3qF4i.cjs";
2
+ export { WriteParagraph, WriteRun, WriteWarning, flattenSectionBlocks };
@@ -1,2 +1,2 @@
1
- import { n as WriteWarning, r as flattenSectionBlocks, t as WriteParagraph } from "../write-SN_aIwyF.js";
2
- export { WriteParagraph, WriteWarning, flattenSectionBlocks };
1
+ import { i as flattenSectionBlocks, n as WriteRun, r as WriteWarning, t as WriteParagraph } from "../write-t4rFgnA-.js";
2
+ export { WriteParagraph, WriteRun, WriteWarning, flattenSectionBlocks };
@@ -1,6 +1,8 @@
1
1
  import { DocFormatError, DocUnsupportedError } from "../errors.js";
2
+ import "../data-stream.js";
2
3
  import "../text/special.js";
3
4
  import { fitsAloneOnPapxPage } from "../prop/fkp-write.js";
5
+ import { buildInlinePicture } from "../pictures-write.js";
4
6
  import { encodeTableRowGrpprl } from "./tap-write.js";
5
7
  //#region src/table/write.ts
6
8
  /** sprmPFInTable (0x2416): a Bool8, "MUST be 1 any time the table depth is greater than zero". */
@@ -8,6 +10,12 @@ const SPRM_P_F_IN_TABLE = 9238;
8
10
  /** sprmPFTtp (0x2417): a Bool8 marking a cell mark as the row's own Table Terminating Paragraph mark. */
9
11
  const SPRM_P_F_TTP = 9239;
10
12
  const TWIPS_PER_POINT = 20;
13
+ function plainRuns(runs) {
14
+ return runs.map((run) => ({
15
+ run,
16
+ extraGrpprl: []
17
+ }));
18
+ }
11
19
  function pushSprm(bytes, opcode, operand) {
12
20
  bytes.push(opcode & 255, opcode >> 8 & 255, ...operand);
13
21
  }
@@ -32,7 +40,7 @@ function cellParagraphs(blocks) {
32
40
  return blocks.map((block, index) => {
33
41
  if (block.kind !== "paragraph") throw new DocUnsupportedError(`doc-codec's writer does not support a '${block.kind}' block inside a table cell (only paragraphs are; nested tables are a separately-tracked gap -- see the README's scope note)`);
34
42
  return {
35
- runs: block.runs,
43
+ runs: plainRuns(block.runs),
36
44
  properties: block,
37
45
  extraGrpprl: inTableGrpprl(),
38
46
  terminator: index === blocks.length - 1 ? 7 : 13
@@ -188,12 +196,25 @@ function flattenTable(table, blockIndex, onWarning) {
188
196
  });
189
197
  return output;
190
198
  }
191
- function flattenSectionBlocks(blocks, onWarning) {
199
+ function imageParagraph(image, dataStream) {
200
+ const { data, buildGrpprl } = buildInlinePicture(image);
201
+ const offset = dataStream.append(data);
202
+ return {
203
+ runs: [{
204
+ run: { text: String.fromCharCode(1) },
205
+ extraGrpprl: buildGrpprl(offset)
206
+ }],
207
+ properties: {},
208
+ extraGrpprl: [],
209
+ terminator: 13
210
+ };
211
+ }
212
+ function flattenSectionBlocks(blocks, dataStream, onWarning) {
192
213
  const output = [];
193
214
  blocks.forEach((block, blockIndex) => {
194
215
  if (block.kind === "paragraph") {
195
216
  output.push({
196
- runs: block.runs,
217
+ runs: plainRuns(block.runs),
197
218
  properties: block,
198
219
  extraGrpprl: [],
199
220
  terminator: 13
@@ -204,6 +225,10 @@ function flattenSectionBlocks(blocks, onWarning) {
204
225
  output.push(...flattenTable(block, blockIndex, onWarning));
205
226
  return;
206
227
  }
228
+ if (block.kind === "image") {
229
+ output.push(imageParagraph(block, dataStream));
230
+ return;
231
+ }
207
232
  throw new DocUnsupportedError(`doc-codec's writer does not yet support '${block.kind}' blocks (see README's scope note)`);
208
233
  });
209
234
  return output;
@@ -1,15 +1,21 @@
1
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.cjs";
1
2
  import { ContentBlock, ContentParagraph, ContentRun } from "document-schema.js";
2
3
  //#region src/table/write.d.ts
3
4
  /** Reports a non-fatal write-time degradation -- this package's own analogue of byte-codec's/pdf-codec's `onWarning`, adopted here rather than a new shape of its own so a caller already handling one already handles the other. */
4
5
  type WriteWarning = (message: string) => void;
6
+ /** One run to write, alongside grpprl bytes appended after encodeCharacterGrpprl's own output for it -- the run-level analogue of WriteParagraph.extraGrpprl below. Every ordinary run carries none (its whole grpprl comes from its own ContentRun fields); imageParagraph's own picture-anchor run is the one exception, since sprmCPicLocation is not a ContentRun field encodeCharacterGrpprl could ever derive on its own. */
7
+ interface WriteRun {
8
+ readonly run: ContentRun;
9
+ readonly extraGrpprl: readonly number[];
10
+ }
5
11
  interface WriteParagraph {
6
- readonly runs: readonly ContentRun[];
12
+ readonly runs: readonly WriteRun[];
7
13
  readonly properties: Pick<ContentParagraph, "alignment" | "indentLeftPt" | "indentRightPt" | "indentFirstLinePt" | "spacingBeforePt" | "spacingAfterPt" | "lineSpacing" | "pageBreakBefore" | "list" | "styleId" | "headingLevel">;
8
14
  /** Extra grpprl bytes appended after encodeParagraphGrpprl's own output -- sprmPFInTable on every table paragraph, plus sprmPFTtp and the row's own TAP on a row's trailing mark. */
9
15
  readonly extraGrpprl: readonly number[];
10
16
  /** The character terminating this paragraph in the text stream: PARAGRAPH_MARK normally, CELL_MARK for a table cell or row mark. */
11
17
  readonly terminator: number;
12
18
  }
13
- declare function flattenSectionBlocks(blocks: readonly ContentBlock[], onWarning?: WriteWarning): WriteParagraph[];
19
+ declare function flattenSectionBlocks(blocks: readonly ContentBlock[], dataStream: DataStreamBuilder, onWarning?: WriteWarning): WriteParagraph[];
14
20
  //#endregion
15
- export { WriteWarning as n, flattenSectionBlocks as r, WriteParagraph as t };
21
+ export { flattenSectionBlocks as i, WriteRun as n, WriteWarning as r, WriteParagraph as t };
@@ -1,15 +1,21 @@
1
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.js";
1
2
  import { ContentBlock, ContentParagraph, ContentRun } from "document-schema.js";
2
3
  //#region src/table/write.d.ts
3
4
  /** Reports a non-fatal write-time degradation -- this package's own analogue of byte-codec's/pdf-codec's `onWarning`, adopted here rather than a new shape of its own so a caller already handling one already handles the other. */
4
5
  type WriteWarning = (message: string) => void;
6
+ /** One run to write, alongside grpprl bytes appended after encodeCharacterGrpprl's own output for it -- the run-level analogue of WriteParagraph.extraGrpprl below. Every ordinary run carries none (its whole grpprl comes from its own ContentRun fields); imageParagraph's own picture-anchor run is the one exception, since sprmCPicLocation is not a ContentRun field encodeCharacterGrpprl could ever derive on its own. */
7
+ interface WriteRun {
8
+ readonly run: ContentRun;
9
+ readonly extraGrpprl: readonly number[];
10
+ }
5
11
  interface WriteParagraph {
6
- readonly runs: readonly ContentRun[];
12
+ readonly runs: readonly WriteRun[];
7
13
  readonly properties: Pick<ContentParagraph, "alignment" | "indentLeftPt" | "indentRightPt" | "indentFirstLinePt" | "spacingBeforePt" | "spacingAfterPt" | "lineSpacing" | "pageBreakBefore" | "list" | "styleId" | "headingLevel">;
8
14
  /** Extra grpprl bytes appended after encodeParagraphGrpprl's own output -- sprmPFInTable on every table paragraph, plus sprmPFTtp and the row's own TAP on a row's trailing mark. */
9
15
  readonly extraGrpprl: readonly number[];
10
16
  /** The character terminating this paragraph in the text stream: PARAGRAPH_MARK normally, CELL_MARK for a table cell or row mark. */
11
17
  readonly terminator: number;
12
18
  }
13
- declare function flattenSectionBlocks(blocks: readonly ContentBlock[], onWarning?: WriteWarning): WriteParagraph[];
19
+ declare function flattenSectionBlocks(blocks: readonly ContentBlock[], dataStream: DataStreamBuilder, onWarning?: WriteWarning): WriteParagraph[];
14
20
  //#endregion
15
- export { WriteWarning as n, flattenSectionBlocks as r, WriteParagraph as t };
21
+ export { flattenSectionBlocks as i, WriteRun as n, WriteWarning as r, WriteParagraph as t };
package/dist/write.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("./errors.cjs");
3
+ const require_data_stream = require("./data-stream.cjs");
3
4
  const require_detect = require("./detect.cjs");
4
5
  require("./prop/fkp.cjs");
5
6
  const require_style_stsh = require("./style/stsh.cjs");
@@ -22,16 +23,22 @@ const TEXT_FC = 1024;
22
23
  const BYTES_PER_CHARACTER = 2;
23
24
  function writeDocContent(document, options = {}) {
24
25
  if (document.kind !== "wordprocessing") throw new require_errors.DocUnsupportedError(`doc-codec writes wordprocessing documents only; got a '${document.kind}' document`);
25
- if (document.sections.length !== 1) throw new require_errors.DocUnsupportedError(`doc-codec's reader never distinguishes more than one section within a document (see README's "Section properties" scope note): writeDocContent refuses ${document.sections.length} sections rather than silently merging their content into what would read back as one`);
26
- const [section] = document.sections;
27
- if (section === void 0) throw new require_errors.DocFormatError("a wordprocessing document must carry a section");
28
- const writeParagraphs = require_table_write.flattenSectionBlocks(section.blocks, options.onWarning);
29
- if (writeParagraphs[writeParagraphs.length - 1]?.terminator !== 13) writeParagraphs.push({
30
- runs: [],
31
- properties: {},
32
- extraGrpprl: [],
33
- terminator: 13
26
+ if (document.sections.length === 0) throw new require_errors.DocFormatError("a wordprocessing document must carry at least one section");
27
+ const dataStream = new require_data_stream.DataStreamBuilder();
28
+ const sectionParagraphLists = document.sections.map((section, index) => {
29
+ const paragraphs = require_table_write.flattenSectionBlocks(section.blocks, dataStream, options.onWarning);
30
+ closeSection(paragraphs, index === document.sections.length - 1 ? 13 : 12);
31
+ return paragraphs;
34
32
  });
33
+ const sectionStartIndices = [];
34
+ {
35
+ let runningIndex = 0;
36
+ for (const paragraphs of sectionParagraphLists) {
37
+ sectionStartIndices.push(runningIndex);
38
+ runningIndex += paragraphs.length;
39
+ }
40
+ }
41
+ const writeParagraphs = sectionParagraphLists.flat();
35
42
  const FIRST_NON_HEADING_ISTD = 10;
36
43
  const MAX_HEADING_ISTD = 9;
37
44
  const styleNames = /* @__PURE__ */ new Map();
@@ -72,9 +79,9 @@ function writeDocContent(document, options = {}) {
72
79
  };
73
80
  const numberingTables = require_list_numbering_write.buildNumberingTables(listUsage.definitions);
74
81
  const formatted = writeParagraphs.map((entry) => ({
75
- runs: entry.runs.map((run) => ({
76
- text: run.text,
77
- grpprl: require_prop_chp_write.encodeCharacterGrpprl(run, fontIndexOf)
82
+ runs: entry.runs.map((writeRun) => ({
83
+ text: writeRun.run.text,
84
+ grpprl: [...require_prop_chp_write.encodeCharacterGrpprl(writeRun.run, fontIndexOf), ...writeRun.extraGrpprl]
78
85
  })),
79
86
  grpprl: [...require_prop_pap_write.encodeParagraphGrpprl(entry.properties, ilfoOf), ...entry.extraGrpprl]
80
87
  }));
@@ -132,9 +139,20 @@ function writeDocContent(document, options = {}) {
132
139
  };
133
140
  });
134
141
  const papxPages = require_prop_fkp_write.buildPapxPages(papxParagraphSpecs, textFcLim);
135
- const sepx = require_prop_sep_write.buildSepx(require_prop_sep_write.encodeSectionGrpprl(section));
136
- const fcSepx = (papxPageStart + papxPages.length) * 512;
137
- const wordDocument = new Uint8Array(fcSepx + sepx.length);
142
+ const sepxPageStart = (papxPageStart + papxPages.length) * 512;
143
+ const sepxList = document.sections.map((section) => require_prop_sep_write.buildSepx(require_prop_sep_write.encodeSectionGrpprl(section)));
144
+ const fcSepxList = [];
145
+ {
146
+ let sepxCursor = sepxPageStart;
147
+ for (const sepx of sepxList) {
148
+ fcSepxList.push(sepxCursor);
149
+ sepxCursor += sepx.length;
150
+ }
151
+ }
152
+ const lastFcSepx = fcSepxList[fcSepxList.length - 1];
153
+ const lastSepx = sepxList[sepxList.length - 1];
154
+ if (lastFcSepx === void 0 || lastSepx === void 0) throw new require_errors.DocFormatError("internal defect: writeDocContent built an empty section list despite the earlier at-least-one-section guard");
155
+ const wordDocument = new Uint8Array(lastFcSepx + lastSepx.length);
138
156
  const wordView = new DataView(wordDocument.buffer);
139
157
  for (let index = 0; index < text.length; index += 1) wordView.setUint16(characterFc(index), text.charCodeAt(index), true);
140
158
  chpxPages.forEach((page, index) => {
@@ -143,13 +161,22 @@ function writeDocContent(document, options = {}) {
143
161
  papxPages.forEach((page, index) => {
144
162
  wordDocument.set(page, (papxPageStart + index) * 512);
145
163
  });
146
- wordDocument.set(sepx, fcSepx);
164
+ sepxList.forEach((sepx, index) => {
165
+ const fcSepx = fcSepxList[index];
166
+ if (fcSepx === void 0) throw new require_errors.DocFormatError(`internal defect: writeDocContent lost section ${String(index)}'s own Sepx placement`);
167
+ wordDocument.set(sepx, fcSepx);
168
+ });
169
+ const sectionStartCps = sectionStartIndices.map((paragraphIndex, index) => {
170
+ const startCp = paragraphStarts[paragraphIndex];
171
+ if (startCp === void 0) throw new require_errors.DocFormatError(`internal defect: writeDocContent lost section ${String(index)}'s own start CP`);
172
+ return startCp;
173
+ });
147
174
  const clx = require_text_piece_table_write.buildTextClx(text.length, TEXT_FC);
148
175
  const chpxBinTable = require_prop_fkp_write.buildPropertyBinTable([...chpxPages.map(require_prop_fkp_write.firstFcOfPage), textFcLim], chpxPages.map((_, index) => chpxPageStart + index));
149
176
  const papxBinTable = require_prop_fkp_write.buildPropertyBinTable([...papxPages.map(require_prop_fkp_write.firstFcOfPage), textFcLim], papxPages.map((_, index) => papxPageStart + index));
150
177
  const stsh = require_style_stsh.buildStshForStyles(styleNames);
151
178
  const fontTable = fontNames.length > 0 ? require_style_fonts.buildFontTable(fontNames) : void 0;
152
- const plcfSed = require_prop_sep_write.buildPlcfSed(text.length, fcSepx);
179
+ const plcfSed = require_prop_sep_write.buildPlcfSed(sectionStartCps, text.length, fcSepxList);
153
180
  let cursor = 0;
154
181
  const place = (bytes) => {
155
182
  const offset = cursor;
@@ -207,11 +234,32 @@ function writeDocContent(document, options = {}) {
207
234
  path: require_detect.SUMMARY_INFORMATION_STREAM,
208
235
  bytes: (0, archive_codec.writeSummaryInformationStream)(require_metadata.layoutMetadataToSummaryInformation(document.metadata))
209
236
  });
237
+ const dataStreamBytes = dataStream.build();
238
+ if (dataStreamBytes.length > 0) streams.push({
239
+ path: require_detect.DATA_STREAM,
240
+ bytes: dataStreamBytes
241
+ });
210
242
  return (0, archive_codec.writeCompoundFile)(streams);
211
243
  }
212
244
  function sameGrpprl(a, b) {
213
245
  if (a === void 0 || b === void 0) return a === b;
214
246
  return a.length === b.length && a.every((byte, index) => byte === b[index]);
215
247
  }
248
+ function closeSection(paragraphs, terminator) {
249
+ if (paragraphs[paragraphs.length - 1]?.terminator !== 13) paragraphs.push({
250
+ runs: [],
251
+ properties: {},
252
+ extraGrpprl: [],
253
+ terminator: 13
254
+ });
255
+ if (terminator === 13) return;
256
+ const index = paragraphs.length - 1;
257
+ const target = paragraphs[index];
258
+ if (target === void 0) throw new require_errors.DocFormatError("internal defect: closeSection lost its own just-ensured trailing paragraph");
259
+ paragraphs[index] = {
260
+ ...target,
261
+ terminator
262
+ };
263
+ }
216
264
  //#endregion
217
265
  exports.writeDocContent = writeDocContent;
package/dist/write.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as WriteWarning } from "./write-SN_aIwyF.cjs";
1
+ import { r as WriteWarning } from "./write-CMB3qF4i.cjs";
2
2
  import { ContentDocument } from "document-schema.js";
3
3
  //#region src/write.d.ts
4
4
  interface WriteDocContentOptions {
package/dist/write.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as WriteWarning } from "./write-SN_aIwyF.js";
1
+ import { r as WriteWarning } from "./write-t4rFgnA-.js";
2
2
  import { ContentDocument } from "document-schema.js";
3
3
  //#region src/write.d.ts
4
4
  interface WriteDocContentOptions {
package/dist/write.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DocFormatError, DocUnsupportedError } from "./errors.js";
2
- import { SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM } from "./detect.js";
2
+ import { DataStreamBuilder } from "./data-stream.js";
3
+ import { DATA_STREAM, SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM } from "./detect.js";
3
4
  import "./prop/fkp.js";
4
5
  import { buildStshForStyles } from "./style/stsh.js";
5
6
  import "./text/special.js";
@@ -21,16 +22,22 @@ const TEXT_FC = 1024;
21
22
  const BYTES_PER_CHARACTER = 2;
22
23
  function writeDocContent(document, options = {}) {
23
24
  if (document.kind !== "wordprocessing") throw new DocUnsupportedError(`doc-codec writes wordprocessing documents only; got a '${document.kind}' document`);
24
- if (document.sections.length !== 1) throw new DocUnsupportedError(`doc-codec's reader never distinguishes more than one section within a document (see README's "Section properties" scope note): writeDocContent refuses ${document.sections.length} sections rather than silently merging their content into what would read back as one`);
25
- const [section] = document.sections;
26
- if (section === void 0) throw new DocFormatError("a wordprocessing document must carry a section");
27
- const writeParagraphs = flattenSectionBlocks(section.blocks, options.onWarning);
28
- if (writeParagraphs[writeParagraphs.length - 1]?.terminator !== 13) writeParagraphs.push({
29
- runs: [],
30
- properties: {},
31
- extraGrpprl: [],
32
- terminator: 13
25
+ if (document.sections.length === 0) throw new DocFormatError("a wordprocessing document must carry at least one section");
26
+ const dataStream = new DataStreamBuilder();
27
+ const sectionParagraphLists = document.sections.map((section, index) => {
28
+ const paragraphs = flattenSectionBlocks(section.blocks, dataStream, options.onWarning);
29
+ closeSection(paragraphs, index === document.sections.length - 1 ? 13 : 12);
30
+ return paragraphs;
33
31
  });
32
+ const sectionStartIndices = [];
33
+ {
34
+ let runningIndex = 0;
35
+ for (const paragraphs of sectionParagraphLists) {
36
+ sectionStartIndices.push(runningIndex);
37
+ runningIndex += paragraphs.length;
38
+ }
39
+ }
40
+ const writeParagraphs = sectionParagraphLists.flat();
34
41
  const FIRST_NON_HEADING_ISTD = 10;
35
42
  const MAX_HEADING_ISTD = 9;
36
43
  const styleNames = /* @__PURE__ */ new Map();
@@ -71,9 +78,9 @@ function writeDocContent(document, options = {}) {
71
78
  };
72
79
  const numberingTables = buildNumberingTables(listUsage.definitions);
73
80
  const formatted = writeParagraphs.map((entry) => ({
74
- runs: entry.runs.map((run) => ({
75
- text: run.text,
76
- grpprl: encodeCharacterGrpprl(run, fontIndexOf)
81
+ runs: entry.runs.map((writeRun) => ({
82
+ text: writeRun.run.text,
83
+ grpprl: [...encodeCharacterGrpprl(writeRun.run, fontIndexOf), ...writeRun.extraGrpprl]
77
84
  })),
78
85
  grpprl: [...encodeParagraphGrpprl(entry.properties, ilfoOf), ...entry.extraGrpprl]
79
86
  }));
@@ -131,9 +138,20 @@ function writeDocContent(document, options = {}) {
131
138
  };
132
139
  });
133
140
  const papxPages = buildPapxPages(papxParagraphSpecs, textFcLim);
134
- const sepx = buildSepx(encodeSectionGrpprl(section));
135
- const fcSepx = (papxPageStart + papxPages.length) * 512;
136
- const wordDocument = new Uint8Array(fcSepx + sepx.length);
141
+ const sepxPageStart = (papxPageStart + papxPages.length) * 512;
142
+ const sepxList = document.sections.map((section) => buildSepx(encodeSectionGrpprl(section)));
143
+ const fcSepxList = [];
144
+ {
145
+ let sepxCursor = sepxPageStart;
146
+ for (const sepx of sepxList) {
147
+ fcSepxList.push(sepxCursor);
148
+ sepxCursor += sepx.length;
149
+ }
150
+ }
151
+ const lastFcSepx = fcSepxList[fcSepxList.length - 1];
152
+ const lastSepx = sepxList[sepxList.length - 1];
153
+ if (lastFcSepx === void 0 || lastSepx === void 0) throw new DocFormatError("internal defect: writeDocContent built an empty section list despite the earlier at-least-one-section guard");
154
+ const wordDocument = new Uint8Array(lastFcSepx + lastSepx.length);
137
155
  const wordView = new DataView(wordDocument.buffer);
138
156
  for (let index = 0; index < text.length; index += 1) wordView.setUint16(characterFc(index), text.charCodeAt(index), true);
139
157
  chpxPages.forEach((page, index) => {
@@ -142,13 +160,22 @@ function writeDocContent(document, options = {}) {
142
160
  papxPages.forEach((page, index) => {
143
161
  wordDocument.set(page, (papxPageStart + index) * 512);
144
162
  });
145
- wordDocument.set(sepx, fcSepx);
163
+ sepxList.forEach((sepx, index) => {
164
+ const fcSepx = fcSepxList[index];
165
+ if (fcSepx === void 0) throw new DocFormatError(`internal defect: writeDocContent lost section ${String(index)}'s own Sepx placement`);
166
+ wordDocument.set(sepx, fcSepx);
167
+ });
168
+ const sectionStartCps = sectionStartIndices.map((paragraphIndex, index) => {
169
+ const startCp = paragraphStarts[paragraphIndex];
170
+ if (startCp === void 0) throw new DocFormatError(`internal defect: writeDocContent lost section ${String(index)}'s own start CP`);
171
+ return startCp;
172
+ });
146
173
  const clx = buildTextClx(text.length, TEXT_FC);
147
174
  const chpxBinTable = buildPropertyBinTable([...chpxPages.map(firstFcOfPage), textFcLim], chpxPages.map((_, index) => chpxPageStart + index));
148
175
  const papxBinTable = buildPropertyBinTable([...papxPages.map(firstFcOfPage), textFcLim], papxPages.map((_, index) => papxPageStart + index));
149
176
  const stsh = buildStshForStyles(styleNames);
150
177
  const fontTable = fontNames.length > 0 ? buildFontTable(fontNames) : void 0;
151
- const plcfSed = buildPlcfSed(text.length, fcSepx);
178
+ const plcfSed = buildPlcfSed(sectionStartCps, text.length, fcSepxList);
152
179
  let cursor = 0;
153
180
  const place = (bytes) => {
154
181
  const offset = cursor;
@@ -206,11 +233,32 @@ function writeDocContent(document, options = {}) {
206
233
  path: SUMMARY_INFORMATION_STREAM,
207
234
  bytes: writeSummaryInformationStream(layoutMetadataToSummaryInformation$1(document.metadata))
208
235
  });
236
+ const dataStreamBytes = dataStream.build();
237
+ if (dataStreamBytes.length > 0) streams.push({
238
+ path: DATA_STREAM,
239
+ bytes: dataStreamBytes
240
+ });
209
241
  return writeCompoundFile(streams);
210
242
  }
211
243
  function sameGrpprl(a, b) {
212
244
  if (a === void 0 || b === void 0) return a === b;
213
245
  return a.length === b.length && a.every((byte, index) => byte === b[index]);
214
246
  }
247
+ function closeSection(paragraphs, terminator) {
248
+ if (paragraphs[paragraphs.length - 1]?.terminator !== 13) paragraphs.push({
249
+ runs: [],
250
+ properties: {},
251
+ extraGrpprl: [],
252
+ terminator: 13
253
+ });
254
+ if (terminator === 13) return;
255
+ const index = paragraphs.length - 1;
256
+ const target = paragraphs[index];
257
+ if (target === void 0) throw new DocFormatError("internal defect: closeSection lost its own just-ensured trailing paragraph");
258
+ paragraphs[index] = {
259
+ ...target,
260
+ terminator
261
+ };
262
+ }
215
263
  //#endregion
216
264
  export { writeDocContent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-codec",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "A hand-written reader for the Word Binary File Format ([MS-DOC], .doc) against the shared document-schema.js content pivot: FIB parsing, piece-table text reconstruction, and CHPX/PAPX formatting exceptions.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -78,7 +78,7 @@
78
78
  "license": "MIT",
79
79
  "packageManager": "pnpm@11.6.0",
80
80
  "dependencies": {
81
- "archive-codec": "1.9.2",
81
+ "archive-codec": "1.10.0",
82
82
  "document-schema.js": "7.5.0"
83
83
  },
84
84
  "devDependencies": {