doc-codec 2.8.6 → 2.10.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.
@@ -22,8 +22,8 @@ function effectiveColumnBoundaryTolerance(definitions) {
22
22
  }
23
23
  return narrowestRealGapTwips === void 0 ? TWIPS_PER_POINT : Math.min(TWIPS_PER_POINT, narrowestRealGapTwips - 1);
24
24
  }
25
- function assembleBlocks(entries) {
26
- return walkBlocksAtDepth(entries, 0);
25
+ function assembleBlocks(entries, documentStreamEnds = false) {
26
+ return walkBlocksAtDepth(entries, 0, documentStreamEnds);
27
27
  }
28
28
  function effectiveTableDepth(properties) {
29
29
  if (properties.tableDepth !== void 0) return properties.tableDepth;
@@ -36,7 +36,7 @@ function isCellBoundary(entry, tableDepth) {
36
36
  function isRowBoundary(entry, tableDepth) {
37
37
  return tableDepth === 1 ? entry.properties.tableRowEnd === true : entry.properties.innerTtpMark === true;
38
38
  }
39
- function walkBlocksAtDepth(entries, depth) {
39
+ function walkBlocksAtDepth(entries, depth, documentStreamEnds) {
40
40
  const blocks = [];
41
41
  let index = 0;
42
42
  while (index < entries.length) {
@@ -49,7 +49,7 @@ function walkBlocksAtDepth(entries, depth) {
49
49
  }
50
50
  const tableDepth = depth + 1;
51
51
  const { runEntries, nextIndex } = collectTableRun(entries, index, tableDepth);
52
- const table = tryAssembleTable(runEntries, tableDepth);
52
+ const table = tryAssembleTable(runEntries, tableDepth, !documentStreamEnds || nextIndex < entries.length);
53
53
  blocks.push(...table !== void 0 ? [table] : runEntries.flatMap((run) => run.blocks));
54
54
  index = nextIndex;
55
55
  }
@@ -69,7 +69,7 @@ function collectTableRun(entries, start, tableDepth) {
69
69
  nextIndex: index
70
70
  };
71
71
  }
72
- function tryAssembleTable(runEntries, tableDepth) {
72
+ function tryAssembleTable(runEntries, tableDepth, streamContinues) {
73
73
  const rawRows = [];
74
74
  const rowDefinitions = [];
75
75
  const rowHeights = [];
@@ -101,11 +101,14 @@ function tryAssembleTable(runEntries, tableDepth) {
101
101
  continue;
102
102
  }
103
103
  if (isCellBoundary(entry, tableDepth)) {
104
- rowCells.push({ blocks: walkBlocksAtDepth(cellEntries, tableDepth) });
104
+ rowCells.push({ blocks: walkBlocksAtDepth(cellEntries, tableDepth, false) });
105
105
  cellEntries = [];
106
106
  }
107
107
  }
108
- if (cellEntries.length > 0 || rowCells.length > 0) throw new require_errors.DocFormatError("a table's paragraphs end without a row-ending mark to close the row's last cell");
108
+ if (cellEntries.length > 0 || rowCells.length > 0) {
109
+ if (rawRows.length === 0 && streamContinues) return;
110
+ throw new require_errors.DocFormatError("a table's paragraphs end without a row-ending mark to close the row's last cell");
111
+ }
109
112
  const toleranceTwips = effectiveColumnBoundaryTolerance(rowDefinitions);
110
113
  const columnBoundariesTwips = canonicalColumnBoundariesTwips(rowDefinitions, toleranceTwips);
111
114
  return {
@@ -1,6 +1,6 @@
1
1
  import { t as ParagraphEntry } from "../paragraphs-DQUp8_HB.cjs";
2
2
  import { ContentBlock } from "document-schema.js";
3
3
  //#region src/table/read.d.ts
4
- declare function assembleBlocks(entries: readonly ParagraphEntry[]): ContentBlock[];
4
+ declare function assembleBlocks(entries: readonly ParagraphEntry[], documentStreamEnds?: boolean): ContentBlock[];
5
5
  //#endregion
6
6
  export { assembleBlocks };
@@ -1,6 +1,6 @@
1
1
  import { t as ParagraphEntry } from "../paragraphs-s2lJ6w2I.js";
2
2
  import { ContentBlock } from "document-schema.js";
3
3
  //#region src/table/read.d.ts
4
- declare function assembleBlocks(entries: readonly ParagraphEntry[]): ContentBlock[];
4
+ declare function assembleBlocks(entries: readonly ParagraphEntry[], documentStreamEnds?: boolean): ContentBlock[];
5
5
  //#endregion
6
6
  export { assembleBlocks };
@@ -21,8 +21,8 @@ function effectiveColumnBoundaryTolerance(definitions) {
21
21
  }
22
22
  return narrowestRealGapTwips === void 0 ? TWIPS_PER_POINT : Math.min(TWIPS_PER_POINT, narrowestRealGapTwips - 1);
23
23
  }
24
- function assembleBlocks(entries) {
25
- return walkBlocksAtDepth(entries, 0);
24
+ function assembleBlocks(entries, documentStreamEnds = false) {
25
+ return walkBlocksAtDepth(entries, 0, documentStreamEnds);
26
26
  }
27
27
  function effectiveTableDepth(properties) {
28
28
  if (properties.tableDepth !== void 0) return properties.tableDepth;
@@ -35,7 +35,7 @@ function isCellBoundary(entry, tableDepth) {
35
35
  function isRowBoundary(entry, tableDepth) {
36
36
  return tableDepth === 1 ? entry.properties.tableRowEnd === true : entry.properties.innerTtpMark === true;
37
37
  }
38
- function walkBlocksAtDepth(entries, depth) {
38
+ function walkBlocksAtDepth(entries, depth, documentStreamEnds) {
39
39
  const blocks = [];
40
40
  let index = 0;
41
41
  while (index < entries.length) {
@@ -48,7 +48,7 @@ function walkBlocksAtDepth(entries, depth) {
48
48
  }
49
49
  const tableDepth = depth + 1;
50
50
  const { runEntries, nextIndex } = collectTableRun(entries, index, tableDepth);
51
- const table = tryAssembleTable(runEntries, tableDepth);
51
+ const table = tryAssembleTable(runEntries, tableDepth, !documentStreamEnds || nextIndex < entries.length);
52
52
  blocks.push(...table !== void 0 ? [table] : runEntries.flatMap((run) => run.blocks));
53
53
  index = nextIndex;
54
54
  }
@@ -68,7 +68,7 @@ function collectTableRun(entries, start, tableDepth) {
68
68
  nextIndex: index
69
69
  };
70
70
  }
71
- function tryAssembleTable(runEntries, tableDepth) {
71
+ function tryAssembleTable(runEntries, tableDepth, streamContinues) {
72
72
  const rawRows = [];
73
73
  const rowDefinitions = [];
74
74
  const rowHeights = [];
@@ -100,11 +100,14 @@ function tryAssembleTable(runEntries, tableDepth) {
100
100
  continue;
101
101
  }
102
102
  if (isCellBoundary(entry, tableDepth)) {
103
- rowCells.push({ blocks: walkBlocksAtDepth(cellEntries, tableDepth) });
103
+ rowCells.push({ blocks: walkBlocksAtDepth(cellEntries, tableDepth, false) });
104
104
  cellEntries = [];
105
105
  }
106
106
  }
107
- if (cellEntries.length > 0 || rowCells.length > 0) throw new DocFormatError("a table's paragraphs end without a row-ending mark to close the row's last cell");
107
+ if (cellEntries.length > 0 || rowCells.length > 0) {
108
+ if (rawRows.length === 0 && streamContinues) return;
109
+ throw new DocFormatError("a table's paragraphs end without a row-ending mark to close the row's last cell");
110
+ }
108
111
  const toleranceTwips = effectiveColumnBoundaryTolerance(rowDefinitions);
109
112
  const columnBoundariesTwips = canonicalColumnBoundariesTwips(rowDefinitions, toleranceTwips);
110
113
  return {
@@ -1,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("../errors.cjs");
3
- require("../data-stream.cjs");
4
3
  require("../text/special.cjs");
5
4
  const require_prop_fkp_write = require("../prop/fkp-write.cjs");
6
5
  const require_pictures_write = require("../pictures-write.cjs");
@@ -210,6 +209,22 @@ function imageParagraph(image, dataStream) {
210
209
  terminator: 13
211
210
  };
212
211
  }
212
+ function appendPageBreak(output) {
213
+ const previous = output[output.length - 1];
214
+ if (previous?.terminator === 13) {
215
+ output[output.length - 1] = {
216
+ ...previous,
217
+ terminator: 12
218
+ };
219
+ return;
220
+ }
221
+ output.push({
222
+ runs: [],
223
+ properties: {},
224
+ extraGrpprl: [],
225
+ terminator: 12
226
+ });
227
+ }
213
228
  function flattenSectionBlocks(blocks, dataStream, onWarning) {
214
229
  const output = [];
215
230
  blocks.forEach((block, blockIndex) => {
@@ -230,6 +245,10 @@ function flattenSectionBlocks(blocks, dataStream, onWarning) {
230
245
  output.push(imageParagraph(block, dataStream));
231
246
  return;
232
247
  }
248
+ if (block.kind === "pageBreak") {
249
+ appendPageBreak(output);
250
+ return;
251
+ }
233
252
  throw new require_errors.DocUnsupportedError(`doc-codec's writer does not yet support '${block.kind}' blocks (see README's scope note)`);
234
253
  });
235
254
  return output;
@@ -1,5 +1,4 @@
1
1
  import { DocFormatError, DocUnsupportedError } from "../errors.js";
2
- import "../data-stream.js";
3
2
  import "../text/special.js";
4
3
  import { fitsAloneOnPapxPage } from "../prop/fkp-write.js";
5
4
  import { buildInlinePicture } from "../pictures-write.js";
@@ -209,6 +208,22 @@ function imageParagraph(image, dataStream) {
209
208
  terminator: 13
210
209
  };
211
210
  }
211
+ function appendPageBreak(output) {
212
+ const previous = output[output.length - 1];
213
+ if (previous?.terminator === 13) {
214
+ output[output.length - 1] = {
215
+ ...previous,
216
+ terminator: 12
217
+ };
218
+ return;
219
+ }
220
+ output.push({
221
+ runs: [],
222
+ properties: {},
223
+ extraGrpprl: [],
224
+ terminator: 12
225
+ });
226
+ }
212
227
  function flattenSectionBlocks(blocks, dataStream, onWarning) {
213
228
  const output = [];
214
229
  blocks.forEach((block, blockIndex) => {
@@ -229,6 +244,10 @@ function flattenSectionBlocks(blocks, dataStream, onWarning) {
229
244
  output.push(imageParagraph(block, dataStream));
230
245
  return;
231
246
  }
247
+ if (block.kind === "pageBreak") {
248
+ appendPageBreak(output);
249
+ return;
250
+ }
232
251
  throw new DocUnsupportedError(`doc-codec's writer does not yet support '${block.kind}' blocks (see README's scope note)`);
233
252
  });
234
253
  return output;
@@ -26,10 +26,26 @@ function readParagraphs(text, fcs, context) {
26
26
  }
27
27
  return entries;
28
28
  }
29
+ /** sprmPHugePapx (0x6646), [MS-DOC] 2.6.2's Paragraph Properties table: "a 4-byte unsigned integer that specifies a location in the Data Stream" where "a PrcData structure begins ... and specifies additional properties for the paragraph". A paragraph whose direct grpprl opens with this sprm keeps its real properties in that Data-stream PrcData -- the whole point of the sprm is a Papx too large for its own 512-byte FKP page, so Word leaves the FKP holding only the pointer. */
30
+ const SPRM_P_HUGE_PAPX = 26182;
31
+ const MAX_PAPX_INDIRECTION_HOPS = 16;
32
+ function resolveIndirectPapx(prls, context) {
33
+ let current = prls;
34
+ for (let hop = 0;; hop += 1) {
35
+ const first = current[0];
36
+ if (first?.sprm.value !== SPRM_P_HUGE_PAPX) return current;
37
+ if (hop >= MAX_PAPX_INDIRECTION_HOPS) throw new require_errors.DocFormatError(`a paragraph's indirect property chain did not terminate within ${MAX_PAPX_INDIRECTION_HOPS} sprmPHugePapx hops; [MS-DOC] 2.6.2 requires the chain to terminate`);
38
+ if (context.dataStream === void 0) throw new require_errors.DocFormatError("a paragraph's grpprl opens with sprmPHugePapx, but this compound file carries no Data stream for it to point into");
39
+ const offset = require_bytes.readUint32LE(first.operand, 0);
40
+ const cbGrpprl = require_bytes.readUint16LE(context.dataStream, offset);
41
+ const grpprl = require_bytes.slice(context.dataStream, offset + 2, cbGrpprl, "a sprmPHugePapx-referenced PrcData's GrpPrl");
42
+ current = require_prop_sprm.readGrpprl(grpprl);
43
+ }
44
+ }
29
45
  function buildParagraph(text, fcs, propertyFc, terminator, endCp, context) {
30
46
  const papx = context.papxTable.papx(propertyFc);
31
47
  const properties = {};
32
- const grpprl = papx !== void 0 ? require_prop_sprm.readGrpprl(papx.grpprl) : [];
48
+ const grpprl = papx !== void 0 ? resolveIndirectPapx(require_prop_sprm.readGrpprl(papx.grpprl), context) : [];
33
49
  if (papx !== void 0) {
34
50
  properties.istd = papx.istd;
35
51
  if (context.styles !== void 0) require_prop_pap.applyParagraphSprms(require_style_stsh.resolveStyleFormatting(context.styles, papx.istd).paragraphPrls, properties);
@@ -1,5 +1,5 @@
1
1
  import { DocFormatError } from "../errors.js";
2
- import { readInt32LE } from "../bytes.js";
2
+ import { readInt32LE, readUint16LE, readUint32LE, slice } from "../bytes.js";
3
3
  import { readGrpprl } from "../prop/sprm.js";
4
4
  import { applyCharacterSprms, characterIstdFromGrpprl } from "../prop/chp.js";
5
5
  import { applyParagraphSprms } from "../prop/pap.js";
@@ -25,10 +25,26 @@ function readParagraphs(text, fcs, context) {
25
25
  }
26
26
  return entries;
27
27
  }
28
+ /** sprmPHugePapx (0x6646), [MS-DOC] 2.6.2's Paragraph Properties table: "a 4-byte unsigned integer that specifies a location in the Data Stream" where "a PrcData structure begins ... and specifies additional properties for the paragraph". A paragraph whose direct grpprl opens with this sprm keeps its real properties in that Data-stream PrcData -- the whole point of the sprm is a Papx too large for its own 512-byte FKP page, so Word leaves the FKP holding only the pointer. */
29
+ const SPRM_P_HUGE_PAPX = 26182;
30
+ const MAX_PAPX_INDIRECTION_HOPS = 16;
31
+ function resolveIndirectPapx(prls, context) {
32
+ let current = prls;
33
+ for (let hop = 0;; hop += 1) {
34
+ const first = current[0];
35
+ if (first?.sprm.value !== SPRM_P_HUGE_PAPX) return current;
36
+ if (hop >= MAX_PAPX_INDIRECTION_HOPS) throw new DocFormatError(`a paragraph's indirect property chain did not terminate within ${MAX_PAPX_INDIRECTION_HOPS} sprmPHugePapx hops; [MS-DOC] 2.6.2 requires the chain to terminate`);
37
+ if (context.dataStream === void 0) throw new DocFormatError("a paragraph's grpprl opens with sprmPHugePapx, but this compound file carries no Data stream for it to point into");
38
+ const offset = readUint32LE(first.operand, 0);
39
+ const cbGrpprl = readUint16LE(context.dataStream, offset);
40
+ const grpprl = slice(context.dataStream, offset + 2, cbGrpprl, "a sprmPHugePapx-referenced PrcData's GrpPrl");
41
+ current = readGrpprl(grpprl);
42
+ }
43
+ }
28
44
  function buildParagraph(text, fcs, propertyFc, terminator, endCp, context) {
29
45
  const papx = context.papxTable.papx(propertyFc);
30
46
  const properties = {};
31
- const grpprl = papx !== void 0 ? readGrpprl(papx.grpprl) : [];
47
+ const grpprl = papx !== void 0 ? resolveIndirectPapx(readGrpprl(papx.grpprl), context) : [];
32
48
  if (papx !== void 0) {
33
49
  properties.istd = papx.istd;
34
50
  if (context.styles !== void 0) applyParagraphSprms(resolveStyleFormatting(context.styles, papx.istd).paragraphPrls, properties);
package/dist/write.cjs CHANGED
@@ -13,8 +13,9 @@ const require_prop_chp_write = require("./prop/chp-write.cjs");
13
13
  const require_prop_pap_write = require("./prop/pap-write.cjs");
14
14
  const require_prop_sep_write = require("./prop/sep-write.cjs");
15
15
  const require_style_fonts = require("./style/fonts.cjs");
16
- const require_list_numbering_write = require("./list/numbering-write.cjs");
17
16
  const require_table_write = require("./table/write.cjs");
17
+ const require_subdocument_write = require("./subdocument-write.cjs");
18
+ const require_list_numbering_write = require("./list/numbering-write.cjs");
18
19
  let archive_codec = require("archive-codec");
19
20
  //#region src/write.ts
20
21
  /** Where the text is written in the WordDocument stream: past the FIB (which needs under 900 bytes for the fields this writer populates), on a page boundary though not required to be. */
@@ -38,7 +39,16 @@ function writeDocContent(document, options = {}) {
38
39
  runningIndex += paragraphs.length;
39
40
  }
40
41
  }
41
- const writeParagraphs = sectionParagraphLists.flat();
42
+ const mainParagraphs = sectionParagraphLists.flat();
43
+ const stories = require_subdocument_write.buildStorySubdocuments(document, dataStream, options.onWarning);
44
+ const ccpText = mainParagraphs.reduce((count, paragraph) => count + require_subdocument_write.paragraphCharacters(paragraph), 0);
45
+ const writeParagraphs = [
46
+ ...mainParagraphs,
47
+ ...stories.footnote?.paragraphs ?? [],
48
+ ...stories.header?.paragraphs ?? [],
49
+ ...stories.comment?.paragraphs ?? [],
50
+ ...stories.endnote?.paragraphs ?? []
51
+ ];
42
52
  const FIRST_NON_HEADING_ISTD = 10;
43
53
  const MAX_HEADING_ISTD = 9;
44
54
  const styleNames = /* @__PURE__ */ new Map();
@@ -176,7 +186,7 @@ function writeDocContent(document, options = {}) {
176
186
  const papxBinTable = require_prop_fkp_write.buildPropertyBinTable([...papxPages.map(require_prop_fkp_write.firstFcOfPage), textFcLim], papxPages.map((_, index) => papxPageStart + index));
177
187
  const stsh = require_style_stsh.buildStshForStyles(styleNames);
178
188
  const fontTable = fontNames.length > 0 ? require_style_fonts.buildFontTable(fontNames) : void 0;
179
- const plcfSed = require_prop_sep_write.buildPlcfSed(sectionStartCps, text.length, fcSepxList);
189
+ const plcfSed = require_prop_sep_write.buildPlcfSed(sectionStartCps, ccpText, fcSepxList);
180
190
  let cursor = 0;
181
191
  const place = (bytes) => {
182
192
  const offset = cursor;
@@ -191,6 +201,10 @@ function writeDocContent(document, options = {}) {
191
201
  const fcSttbfFfn = fontTable !== void 0 ? place(fontTable) : 0;
192
202
  const fcPlfLst = numberingTables !== void 0 ? place(numberingTables.plfLst) : 0;
193
203
  const fcPlfLfo = numberingTables !== void 0 ? place(numberingTables.plfLfo) : 0;
204
+ const fcPlcffndTxt = stories.footnote !== void 0 ? place(stories.footnote.plex) : 0;
205
+ const fcPlcfHdd = stories.header !== void 0 ? place(stories.header.plex) : 0;
206
+ const fcPlcfandTxt = stories.comment !== void 0 ? place(stories.comment.plex) : 0;
207
+ const fcPlcfendTxt = stories.endnote !== void 0 ? place(stories.endnote.plex) : 0;
194
208
  const table = new Uint8Array(cursor);
195
209
  table.set(clx, fcClx);
196
210
  table.set(chpxBinTable, fcPlcfBteChpx);
@@ -202,8 +216,12 @@ function writeDocContent(document, options = {}) {
202
216
  table.set(numberingTables.plfLst, fcPlfLst);
203
217
  table.set(numberingTables.plfLfo, fcPlfLfo);
204
218
  }
219
+ if (stories.footnote !== void 0) table.set(stories.footnote.plex, fcPlcffndTxt);
220
+ if (stories.header !== void 0) table.set(stories.header.plex, fcPlcfHdd);
221
+ if (stories.comment !== void 0) table.set(stories.comment.plex, fcPlcfandTxt);
222
+ if (stories.endnote !== void 0) table.set(stories.endnote.plex, fcPlcfendTxt);
205
223
  const fib = require_fib_write.buildFib({
206
- ccpText: text.length,
224
+ ccpText,
207
225
  cbMac: wordDocument.length,
208
226
  fcClx,
209
227
  lcbClx: clx.length,
@@ -220,7 +238,19 @@ function writeDocContent(document, options = {}) {
220
238
  fcPlfLst,
221
239
  lcbPlfLst: numberingTables?.lcbPlfLst ?? 0,
222
240
  fcPlfLfo,
223
- lcbPlfLfo: numberingTables?.plfLfo.length ?? 0
241
+ lcbPlfLfo: numberingTables?.plfLfo.length ?? 0,
242
+ ccpFtn: stories.footnote?.ccp ?? 0,
243
+ fcPlcffndTxt,
244
+ lcbPlcffndTxt: stories.footnote?.plex.length ?? 0,
245
+ ccpHdd: stories.header?.ccp ?? 0,
246
+ fcPlcfHdd,
247
+ lcbPlcfHdd: stories.header?.plex.length ?? 0,
248
+ ccpAtn: stories.comment?.ccp ?? 0,
249
+ fcPlcfandTxt,
250
+ lcbPlcfandTxt: stories.comment?.plex.length ?? 0,
251
+ ccpEdn: stories.endnote?.ccp ?? 0,
252
+ fcPlcfendTxt,
253
+ lcbPlcfendTxt: stories.endnote?.plex.length ?? 0
224
254
  });
225
255
  wordDocument.set(fib, 0);
226
256
  const streams = [{
package/dist/write.d.cts CHANGED
@@ -1,10 +1,19 @@
1
+ import { HeaderFooterStory } from "./headers-footers.cjs";
1
2
  import { r as WriteWarning } from "./write-CMB3qF4i.cjs";
3
+ import { Comment, Footnote } from "./notes.cjs";
2
4
  import { ContentDocument } from "document-schema.js";
3
5
  //#region src/write.d.ts
4
6
  interface WriteDocContentOptions {
5
7
  /** Reports a non-fatal write-time degradation -- today, only table/write.ts's own per-row lost-boundary-budget fallback (ExaDev/documents.js#1013), the same `onWarning` shape byte-codec's PNG decoder and pdf-codec already use for a recoverable, non-fatal defect. Not a guarantee the write itself goes on to succeed: when a row's own assigned lost boundaries can't be trimmed down to a split that fits at all, this still fires once -- reporting that the row's boundaries could not be stated and that its fully-unsplit encoding is being attempted instead -- before writeDocContent can discover, further down the same pipeline, that even that unsplit encoding overflows the row's own byte budget and throws its usual DocFormatError; the warning describes what this fallback could not recover, not a promise that a hard failure won't immediately follow it. It is never called in place of a genuine refusal this writer makes outright (an unsupported block kind, more than one section, and so on) -- those always throw DocFormatError/DocUnsupportedError directly, with no warning first. */
6
8
  readonly onWarning?: WriteWarning;
7
9
  }
8
- declare function writeDocContent(document: ContentDocument, options?: WriteDocContentOptions): Uint8Array<ArrayBuffer>;
10
+ /** writeDocContent's own input: a `ContentDocument` widened by the same story fields `readDocContent`'s own `DocContent` output carries (read.ts) -- footnotes, endnotes, comments, and header/footer stories -- each OPTIONAL, exactly the shape ooxml.js's own `DocxContent` input already established for the identical constructs, so a plain `ContentDocument` (none of the four stated) still writes exactly as it always did, while a genuine `DocContent` assigns straight across. A type-alias intersection rather than an interface for the identical reason DocContent is one: ContentDocument is a union of document kinds, and only the intersection spreads its members statically. The stories are written back as genuine subdocuments (subdocument-write.ts); a document that states none writes no subdocument at all, with every story ccp and fc/lcb pair left zero. */
11
+ type WritableDocContent = ContentDocument & {
12
+ readonly footnotes?: readonly Footnote[];
13
+ readonly endnotes?: readonly Footnote[];
14
+ readonly comments?: readonly Comment[];
15
+ readonly headerFooterStories?: readonly HeaderFooterStory[];
16
+ };
17
+ declare function writeDocContent(document: WritableDocContent, options?: WriteDocContentOptions): Uint8Array<ArrayBuffer>;
9
18
  //#endregion
10
- export { WriteDocContentOptions, writeDocContent };
19
+ export { WritableDocContent, WriteDocContentOptions, writeDocContent };
package/dist/write.d.ts CHANGED
@@ -1,10 +1,19 @@
1
+ import { HeaderFooterStory } from "./headers-footers.js";
1
2
  import { r as WriteWarning } from "./write-t4rFgnA-.js";
3
+ import { Comment, Footnote } from "./notes.js";
2
4
  import { ContentDocument } from "document-schema.js";
3
5
  //#region src/write.d.ts
4
6
  interface WriteDocContentOptions {
5
7
  /** Reports a non-fatal write-time degradation -- today, only table/write.ts's own per-row lost-boundary-budget fallback (ExaDev/documents.js#1013), the same `onWarning` shape byte-codec's PNG decoder and pdf-codec already use for a recoverable, non-fatal defect. Not a guarantee the write itself goes on to succeed: when a row's own assigned lost boundaries can't be trimmed down to a split that fits at all, this still fires once -- reporting that the row's boundaries could not be stated and that its fully-unsplit encoding is being attempted instead -- before writeDocContent can discover, further down the same pipeline, that even that unsplit encoding overflows the row's own byte budget and throws its usual DocFormatError; the warning describes what this fallback could not recover, not a promise that a hard failure won't immediately follow it. It is never called in place of a genuine refusal this writer makes outright (an unsupported block kind, more than one section, and so on) -- those always throw DocFormatError/DocUnsupportedError directly, with no warning first. */
6
8
  readonly onWarning?: WriteWarning;
7
9
  }
8
- declare function writeDocContent(document: ContentDocument, options?: WriteDocContentOptions): Uint8Array<ArrayBuffer>;
10
+ /** writeDocContent's own input: a `ContentDocument` widened by the same story fields `readDocContent`'s own `DocContent` output carries (read.ts) -- footnotes, endnotes, comments, and header/footer stories -- each OPTIONAL, exactly the shape ooxml.js's own `DocxContent` input already established for the identical constructs, so a plain `ContentDocument` (none of the four stated) still writes exactly as it always did, while a genuine `DocContent` assigns straight across. A type-alias intersection rather than an interface for the identical reason DocContent is one: ContentDocument is a union of document kinds, and only the intersection spreads its members statically. The stories are written back as genuine subdocuments (subdocument-write.ts); a document that states none writes no subdocument at all, with every story ccp and fc/lcb pair left zero. */
11
+ type WritableDocContent = ContentDocument & {
12
+ readonly footnotes?: readonly Footnote[];
13
+ readonly endnotes?: readonly Footnote[];
14
+ readonly comments?: readonly Comment[];
15
+ readonly headerFooterStories?: readonly HeaderFooterStory[];
16
+ };
17
+ declare function writeDocContent(document: WritableDocContent, options?: WriteDocContentOptions): Uint8Array<ArrayBuffer>;
9
18
  //#endregion
10
- export { WriteDocContentOptions, writeDocContent };
19
+ export { WritableDocContent, WriteDocContentOptions, writeDocContent };
package/dist/write.js CHANGED
@@ -12,8 +12,9 @@ import { encodeCharacterGrpprl } from "./prop/chp-write.js";
12
12
  import { encodeParagraphGrpprl } from "./prop/pap-write.js";
13
13
  import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.js";
14
14
  import { buildFontTable } from "./style/fonts.js";
15
- import { buildNumberingTables, gatherListUsage } from "./list/numbering-write.js";
16
15
  import { flattenSectionBlocks } from "./table/write.js";
16
+ import { buildStorySubdocuments, paragraphCharacters } from "./subdocument-write.js";
17
+ import { buildNumberingTables, gatherListUsage } from "./list/numbering-write.js";
17
18
  import { hasSummaryInformationFields, writeCompoundFile, writeSummaryInformationStream } from "archive-codec";
18
19
  //#region src/write.ts
19
20
  /** Where the text is written in the WordDocument stream: past the FIB (which needs under 900 bytes for the fields this writer populates), on a page boundary though not required to be. */
@@ -37,7 +38,16 @@ function writeDocContent(document, options = {}) {
37
38
  runningIndex += paragraphs.length;
38
39
  }
39
40
  }
40
- const writeParagraphs = sectionParagraphLists.flat();
41
+ const mainParagraphs = sectionParagraphLists.flat();
42
+ const stories = buildStorySubdocuments(document, dataStream, options.onWarning);
43
+ const ccpText = mainParagraphs.reduce((count, paragraph) => count + paragraphCharacters(paragraph), 0);
44
+ const writeParagraphs = [
45
+ ...mainParagraphs,
46
+ ...stories.footnote?.paragraphs ?? [],
47
+ ...stories.header?.paragraphs ?? [],
48
+ ...stories.comment?.paragraphs ?? [],
49
+ ...stories.endnote?.paragraphs ?? []
50
+ ];
41
51
  const FIRST_NON_HEADING_ISTD = 10;
42
52
  const MAX_HEADING_ISTD = 9;
43
53
  const styleNames = /* @__PURE__ */ new Map();
@@ -175,7 +185,7 @@ function writeDocContent(document, options = {}) {
175
185
  const papxBinTable = buildPropertyBinTable([...papxPages.map(firstFcOfPage), textFcLim], papxPages.map((_, index) => papxPageStart + index));
176
186
  const stsh = buildStshForStyles(styleNames);
177
187
  const fontTable = fontNames.length > 0 ? buildFontTable(fontNames) : void 0;
178
- const plcfSed = buildPlcfSed(sectionStartCps, text.length, fcSepxList);
188
+ const plcfSed = buildPlcfSed(sectionStartCps, ccpText, fcSepxList);
179
189
  let cursor = 0;
180
190
  const place = (bytes) => {
181
191
  const offset = cursor;
@@ -190,6 +200,10 @@ function writeDocContent(document, options = {}) {
190
200
  const fcSttbfFfn = fontTable !== void 0 ? place(fontTable) : 0;
191
201
  const fcPlfLst = numberingTables !== void 0 ? place(numberingTables.plfLst) : 0;
192
202
  const fcPlfLfo = numberingTables !== void 0 ? place(numberingTables.plfLfo) : 0;
203
+ const fcPlcffndTxt = stories.footnote !== void 0 ? place(stories.footnote.plex) : 0;
204
+ const fcPlcfHdd = stories.header !== void 0 ? place(stories.header.plex) : 0;
205
+ const fcPlcfandTxt = stories.comment !== void 0 ? place(stories.comment.plex) : 0;
206
+ const fcPlcfendTxt = stories.endnote !== void 0 ? place(stories.endnote.plex) : 0;
193
207
  const table = new Uint8Array(cursor);
194
208
  table.set(clx, fcClx);
195
209
  table.set(chpxBinTable, fcPlcfBteChpx);
@@ -201,8 +215,12 @@ function writeDocContent(document, options = {}) {
201
215
  table.set(numberingTables.plfLst, fcPlfLst);
202
216
  table.set(numberingTables.plfLfo, fcPlfLfo);
203
217
  }
218
+ if (stories.footnote !== void 0) table.set(stories.footnote.plex, fcPlcffndTxt);
219
+ if (stories.header !== void 0) table.set(stories.header.plex, fcPlcfHdd);
220
+ if (stories.comment !== void 0) table.set(stories.comment.plex, fcPlcfandTxt);
221
+ if (stories.endnote !== void 0) table.set(stories.endnote.plex, fcPlcfendTxt);
204
222
  const fib = buildFib({
205
- ccpText: text.length,
223
+ ccpText,
206
224
  cbMac: wordDocument.length,
207
225
  fcClx,
208
226
  lcbClx: clx.length,
@@ -219,7 +237,19 @@ function writeDocContent(document, options = {}) {
219
237
  fcPlfLst,
220
238
  lcbPlfLst: numberingTables?.lcbPlfLst ?? 0,
221
239
  fcPlfLfo,
222
- lcbPlfLfo: numberingTables?.plfLfo.length ?? 0
240
+ lcbPlfLfo: numberingTables?.plfLfo.length ?? 0,
241
+ ccpFtn: stories.footnote?.ccp ?? 0,
242
+ fcPlcffndTxt,
243
+ lcbPlcffndTxt: stories.footnote?.plex.length ?? 0,
244
+ ccpHdd: stories.header?.ccp ?? 0,
245
+ fcPlcfHdd,
246
+ lcbPlcfHdd: stories.header?.plex.length ?? 0,
247
+ ccpAtn: stories.comment?.ccp ?? 0,
248
+ fcPlcfandTxt,
249
+ lcbPlcfandTxt: stories.comment?.plex.length ?? 0,
250
+ ccpEdn: stories.endnote?.ccp ?? 0,
251
+ fcPlcfendTxt,
252
+ lcbPlcfendTxt: stories.endnote?.plex.length ?? 0
223
253
  });
224
254
  wordDocument.set(fib, 0);
225
255
  const streams = [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-codec",
3
- "version": "2.8.6",
3
+ "version": "2.10.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": {
@@ -80,8 +80,8 @@
80
80
  "license": "MIT",
81
81
  "packageManager": "pnpm@11.6.0",
82
82
  "dependencies": {
83
- "archive-codec": "1.10.8",
84
- "document-schema.js": "7.10.0"
83
+ "archive-codec": "1.11.0",
84
+ "document-schema.js": "7.11.0"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@arethetypeswrong/cli": "0.18.5",