doc-codec 2.8.6 → 2.9.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/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { readParagraphs, splitEntriesByBoundaries } from "./text/paragraphs.js";
15
15
  import { characterOffset, characterSize, parseClx } from "./text/piece-table.js";
16
16
  import { COMPRESSED_CHARACTER_MAP, readTextRange } from "./text/characters.js";
17
17
  import { readSubdocumentStories, storyText } from "./subdocument.js";
18
- import { readHeaderFooterStories } from "./headers-footers.js";
18
+ import { FIXED_SEPARATOR_STORY_COUNT, SLOT_ORDER, readHeaderFooterStories } from "./headers-footers.js";
19
19
  import { parseFib, peekFibBaseFlags, tableStreamName } from "./fib/fib.js";
20
20
  import { buildFib } from "./fib/write.js";
21
21
  import { layoutMetadataToSummaryInformation } from "./metadata.js";
@@ -27,7 +27,8 @@ import { applySectionSprms, readAllSectionProperties } from "./prop/sep.js";
27
27
  import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.js";
28
28
  import { buildFontTable, parseFontTable } from "./style/fonts.js";
29
29
  import { NUMBER_FORMAT_BY_NFC, readNumberingDefinitions } from "./list/numbering.js";
30
+ import { buildHeaderSubdocument, buildNoteSubdocument, buildStorySubdocuments, paragraphCharacters } from "./subdocument-write.js";
30
31
  import { readNoteBodies } from "./notes.js";
31
32
  import { readDocContent, readDocStreams } from "./read.js";
32
33
  import { writeDocContent } from "./write.js";
33
- export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DATA_STREAM, DRAWN_OBJECT, DataStreamBuilder, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, base64ToBytes, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, bytesToBase64, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
34
+ export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DATA_STREAM, DRAWN_OBJECT, DataStreamBuilder, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FIXED_SEPARATOR_STORY_COUNT, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, SLOT_ORDER, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, base64ToBytes, buildChpxPages, buildFib, buildFontTable, buildHeaderSubdocument, buildNoteSubdocument, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStorySubdocuments, buildStshForStyles, buildTextClx, bytesToBase64, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, paragraphCharacters, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
package/dist/read.cjs CHANGED
@@ -5,6 +5,7 @@ const require_detect = require("./detect.cjs");
5
5
  const require_encryption = require("./encryption.cjs");
6
6
  const require_prop_fkp = require("./prop/fkp.cjs");
7
7
  const require_style_stsh = require("./style/stsh.cjs");
8
+ require("./text/special.cjs");
8
9
  const require_text_paragraphs = require("./text/paragraphs.cjs");
9
10
  const require_text_piece_table = require("./text/piece-table.cjs");
10
11
  const require_text_characters = require("./text/characters.cjs");
@@ -70,7 +71,7 @@ function readDocContent(bytes, password) {
70
71
  const entries = require_text_paragraphs.readParagraphs(range.text, range.fcs, context);
71
72
  const numbering = require_list_numbering.readNumberingDefinitions(table, fib);
72
73
  const sectionProperties = require_prop_sep.readAllSectionProperties(wordDocument, table, fib);
73
- const entriesBySection = splitIntoSections(entries, sectionProperties);
74
+ const entriesBySection = splitIntoSections(markManualPageBreaks(entries, sectionProperties), sectionProperties);
74
75
  const { footnotes, endnotes, comments } = require_notes.readNoteBodies(wordDocument, table, pieceTable, context, fib);
75
76
  const headerFooterStories = require_headers_footers.readHeaderFooterStories(wordDocument, table, pieceTable, context, fib, sectionProperties.length);
76
77
  return {
@@ -96,6 +97,22 @@ function readDocContent(bytes, password) {
96
97
  headerFooterStories
97
98
  };
98
99
  }
100
+ /** The single block a manual page break reads as -- no fields of its own, exactly the shape every other codec in this family emits for one. */
101
+ const PAGE_BREAK_BLOCK = { kind: "pageBreak" };
102
+ function markManualPageBreaks(entries, sections) {
103
+ const sectionEndCps = /* @__PURE__ */ new Set();
104
+ for (let index = 1; index < sections.length; index += 1) {
105
+ const startCp = sections[index]?.startCp;
106
+ if (startCp !== void 0) sectionEndCps.add(startCp);
107
+ }
108
+ return entries.map((entry) => {
109
+ if (entry.terminator !== 12 || sectionEndCps.has(entry.endCp)) return entry;
110
+ return {
111
+ ...entry,
112
+ blocks: [...entry.blocks, PAGE_BREAK_BLOCK]
113
+ };
114
+ });
115
+ }
99
116
  function splitIntoSections(entries, sections) {
100
117
  const groups = sections.map(() => []);
101
118
  let sectionIndex = 0;
package/dist/read.js CHANGED
@@ -4,6 +4,7 @@ import { DATA_STREAM, SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM } from ".
4
4
  import { decryptDocStreams } from "./encryption.js";
5
5
  import { PropertyBinTable } from "./prop/fkp.js";
6
6
  import { parseStsh } from "./style/stsh.js";
7
+ import "./text/special.js";
7
8
  import { readParagraphs } from "./text/paragraphs.js";
8
9
  import { parseClx } from "./text/piece-table.js";
9
10
  import { readTextRange } from "./text/characters.js";
@@ -69,7 +70,7 @@ function readDocContent(bytes, password) {
69
70
  const entries = readParagraphs(range.text, range.fcs, context);
70
71
  const numbering = readNumberingDefinitions(table, fib);
71
72
  const sectionProperties = readAllSectionProperties(wordDocument, table, fib);
72
- const entriesBySection = splitIntoSections(entries, sectionProperties);
73
+ const entriesBySection = splitIntoSections(markManualPageBreaks(entries, sectionProperties), sectionProperties);
73
74
  const { footnotes, endnotes, comments } = readNoteBodies(wordDocument, table, pieceTable, context, fib);
74
75
  const headerFooterStories = readHeaderFooterStories(wordDocument, table, pieceTable, context, fib, sectionProperties.length);
75
76
  return {
@@ -95,6 +96,22 @@ function readDocContent(bytes, password) {
95
96
  headerFooterStories
96
97
  };
97
98
  }
99
+ /** The single block a manual page break reads as -- no fields of its own, exactly the shape every other codec in this family emits for one. */
100
+ const PAGE_BREAK_BLOCK = { kind: "pageBreak" };
101
+ function markManualPageBreaks(entries, sections) {
102
+ const sectionEndCps = /* @__PURE__ */ new Set();
103
+ for (let index = 1; index < sections.length; index += 1) {
104
+ const startCp = sections[index]?.startCp;
105
+ if (startCp !== void 0) sectionEndCps.add(startCp);
106
+ }
107
+ return entries.map((entry) => {
108
+ if (entry.terminator !== 12 || sectionEndCps.has(entry.endCp)) return entry;
109
+ return {
110
+ ...entry,
111
+ blocks: [...entry.blocks, PAGE_BREAK_BLOCK]
112
+ };
113
+ });
114
+ }
98
115
  function splitIntoSections(entries, sections) {
99
116
  const groups = sections.map(() => []);
100
117
  let sectionIndex = 0;
@@ -0,0 +1,108 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("./errors.cjs");
3
+ require("./text/special.cjs");
4
+ const require_headers_footers = require("./headers-footers.cjs");
5
+ const require_table_write = require("./table/write.cjs");
6
+ //#region src/subdocument-write.ts
7
+ /** A plain one-run text run: no character formatting, no extra grpprl -- a note body's own text carries no modelled formatting to state. */
8
+ function textRun(text) {
9
+ return {
10
+ run: { text },
11
+ extraGrpprl: []
12
+ };
13
+ }
14
+ /** The bare paragraph mark every non-empty story ends with -- no runs, no properties, the "not considered part of the story contents" guard itself. */
15
+ function guardParagraph() {
16
+ return {
17
+ runs: [],
18
+ properties: {},
19
+ extraGrpprl: [],
20
+ terminator: 13
21
+ };
22
+ }
23
+ /** One paragraph's own character footprint in the text stream: every run's own characters plus the paragraph's own terminator. Exported because write.ts counts the main document's own ccpText with the identical arithmetic -- one source, so the two can never disagree about what a paragraph costs. */
24
+ function paragraphCharacters(paragraph) {
25
+ return 1 + paragraph.runs.reduce((count, run) => count + run.run.text.length, 0);
26
+ }
27
+ /** A CP-only plex (element size 0, [MS-DOC] 2.2.2's own "the data thus has a size of 0 bytes" shape PlcffndTxt/PlcfandTxt/PlcfendTxt/PlcfHdd all share): just the aCP array, one 4-byte little-endian value per key. */
28
+ function buildCpOnlyPlex(keys) {
29
+ const bytes = new Uint8Array(keys.length * 4);
30
+ const view = new DataView(bytes.buffer);
31
+ keys.forEach((key, index) => {
32
+ view.setUint32(index * 4, key, true);
33
+ });
34
+ return bytes;
35
+ }
36
+ function finishSubdocument(paragraphs, storyStarts, characters) {
37
+ paragraphs.push(guardParagraph());
38
+ const ccp = characters + 1;
39
+ return {
40
+ paragraphs,
41
+ plex: buildCpOnlyPlex([
42
+ ...storyStarts,
43
+ characters,
44
+ ccp
45
+ ]),
46
+ ccp
47
+ };
48
+ }
49
+ function buildNoteSubdocument(notes) {
50
+ const paragraphs = [];
51
+ const storyStarts = [];
52
+ let characters = 0;
53
+ for (const note of notes) {
54
+ storyStarts.push(characters);
55
+ const story = note.text.split("\n").map((line) => ({
56
+ runs: line === "" ? [] : [textRun(line)],
57
+ properties: {},
58
+ extraGrpprl: [],
59
+ terminator: 13
60
+ }));
61
+ story.push(guardParagraph());
62
+ for (const paragraph of story) characters += paragraphCharacters(paragraph);
63
+ paragraphs.push(...story);
64
+ }
65
+ return finishSubdocument(paragraphs, storyStarts, characters);
66
+ }
67
+ function buildHeaderSubdocument(stories, sectionCount, dataStream, onWarning) {
68
+ const bySlot = /* @__PURE__ */ new Map();
69
+ for (const story of stories) {
70
+ if (!Number.isInteger(story.section) || story.section < 0 || story.section >= sectionCount) throw new require_errors.DocFormatError(`a header/footer story names section ${String(story.section)}, but this document has ${String(sectionCount)} sections`);
71
+ const key = `${String(story.section)}:${story.slot}`;
72
+ if (bySlot.has(key)) throw new require_errors.DocFormatError(`more than one header/footer story was given for section ${String(story.section)}'s ${story.slot} slot; a slot holds one story`);
73
+ bySlot.set(key, story);
74
+ }
75
+ const paragraphs = [];
76
+ const storyStarts = [];
77
+ let characters = 0;
78
+ for (let count = 0; count < 6; count += 1) storyStarts.push(0);
79
+ for (let section = 0; section < sectionCount; section += 1) for (const slot of require_headers_footers.SLOT_ORDER) {
80
+ storyStarts.push(characters);
81
+ const story = bySlot.get(`${String(section)}:${slot}`);
82
+ if (story === void 0) continue;
83
+ let content = require_table_write.flattenSectionBlocks(story.blocks, dataStream, onWarning);
84
+ if (content.length === 0) content = [{
85
+ runs: [],
86
+ properties: {},
87
+ extraGrpprl: [],
88
+ terminator: 13
89
+ }];
90
+ content.push(guardParagraph());
91
+ for (const paragraph of content) characters += paragraphCharacters(paragraph);
92
+ paragraphs.push(...content);
93
+ }
94
+ return finishSubdocument(paragraphs, storyStarts, characters);
95
+ }
96
+ function buildStorySubdocuments(document, dataStream, onWarning) {
97
+ return {
98
+ footnote: document.footnotes !== void 0 && document.footnotes.length > 0 ? buildNoteSubdocument(document.footnotes) : void 0,
99
+ header: document.headerFooterStories !== void 0 && document.headerFooterStories.length > 0 ? buildHeaderSubdocument(document.headerFooterStories, document.sections.length, dataStream, onWarning) : void 0,
100
+ comment: document.comments !== void 0 && document.comments.length > 0 ? buildNoteSubdocument(document.comments) : void 0,
101
+ endnote: document.endnotes !== void 0 && document.endnotes.length > 0 ? buildNoteSubdocument(document.endnotes) : void 0
102
+ };
103
+ }
104
+ //#endregion
105
+ exports.buildHeaderSubdocument = buildHeaderSubdocument;
106
+ exports.buildNoteSubdocument = buildNoteSubdocument;
107
+ exports.buildStorySubdocuments = buildStorySubdocuments;
108
+ exports.paragraphCharacters = paragraphCharacters;
@@ -0,0 +1,42 @@
1
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.cjs";
2
+ import { HeaderFooterStory } from "./headers-footers.cjs";
3
+ import { r as WriteWarning, t as WriteParagraph } from "./write-CMB3qF4i.cjs";
4
+ import { ContentDocument } from "document-schema.js";
5
+ //#region src/subdocument-write.d.ts
6
+ /** One written subdocument: its paragraphs (to be appended after the main document's own, in [MS-DOC]'s own subdocument order), the CP-only plex dividing them into stories, and the ccp the FIB states for it. `undefined` never appears here -- a subdocument with no stories is not written at all (write.ts leaves its ccp and fc/lcb pairs zero, exactly what the reader treats as "absent"). */
7
+ interface StorySubdocument {
8
+ readonly paragraphs: readonly WriteParagraph[];
9
+ readonly plex: Uint8Array<ArrayBuffer>;
10
+ readonly ccp: number;
11
+ }
12
+ /** One paragraph's own character footprint in the text stream: every run's own characters plus the paragraph's own terminator. Exported because write.ts counts the main document's own ccpText with the identical arithmetic -- one source, so the two can never disagree about what a paragraph costs. */
13
+ declare function paragraphCharacters(paragraph: WriteParagraph): number;
14
+ declare function buildNoteSubdocument(notes: readonly {
15
+ readonly text: string;
16
+ }[]): StorySubdocument;
17
+ declare function buildHeaderSubdocument(stories: readonly HeaderFooterStory[], sectionCount: number, dataStream: DataStreamBuilder, onWarning?: WriteWarning): StorySubdocument;
18
+ /** Every story subdocument a document carries, in [MS-DOC]'s own concatenation order (footnote, header, comment, endnote -- the same order notes.ts/read.ts count ccp boundaries in), each undefined when the document carries none of that kind. */
19
+ interface StorySubdocuments {
20
+ readonly footnote: StorySubdocument | undefined;
21
+ readonly header: StorySubdocument | undefined;
22
+ readonly comment: StorySubdocument | undefined;
23
+ readonly endnote: StorySubdocument | undefined;
24
+ }
25
+ /** The input type's own optional story fields, isolated here so buildStorySubdocuments states its shape once -- writeDocContent's WritableDocContent satisfies it directly. */
26
+ interface StorySources {
27
+ readonly footnotes?: readonly {
28
+ readonly text: string;
29
+ }[];
30
+ readonly endnotes?: readonly {
31
+ readonly text: string;
32
+ }[];
33
+ readonly comments?: readonly {
34
+ readonly text: string;
35
+ }[];
36
+ readonly headerFooterStories?: readonly HeaderFooterStory[];
37
+ }
38
+ declare function buildStorySubdocuments(document: StorySources & Pick<Extract<ContentDocument, {
39
+ kind: "wordprocessing";
40
+ }>, "sections">, dataStream: DataStreamBuilder, onWarning?: WriteWarning): StorySubdocuments;
41
+ //#endregion
42
+ export { StorySubdocument, StorySubdocuments, buildHeaderSubdocument, buildNoteSubdocument, buildStorySubdocuments, paragraphCharacters };
@@ -0,0 +1,42 @@
1
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.js";
2
+ import { HeaderFooterStory } from "./headers-footers.js";
3
+ import { r as WriteWarning, t as WriteParagraph } from "./write-t4rFgnA-.js";
4
+ import { ContentDocument } from "document-schema.js";
5
+ //#region src/subdocument-write.d.ts
6
+ /** One written subdocument: its paragraphs (to be appended after the main document's own, in [MS-DOC]'s own subdocument order), the CP-only plex dividing them into stories, and the ccp the FIB states for it. `undefined` never appears here -- a subdocument with no stories is not written at all (write.ts leaves its ccp and fc/lcb pairs zero, exactly what the reader treats as "absent"). */
7
+ interface StorySubdocument {
8
+ readonly paragraphs: readonly WriteParagraph[];
9
+ readonly plex: Uint8Array<ArrayBuffer>;
10
+ readonly ccp: number;
11
+ }
12
+ /** One paragraph's own character footprint in the text stream: every run's own characters plus the paragraph's own terminator. Exported because write.ts counts the main document's own ccpText with the identical arithmetic -- one source, so the two can never disagree about what a paragraph costs. */
13
+ declare function paragraphCharacters(paragraph: WriteParagraph): number;
14
+ declare function buildNoteSubdocument(notes: readonly {
15
+ readonly text: string;
16
+ }[]): StorySubdocument;
17
+ declare function buildHeaderSubdocument(stories: readonly HeaderFooterStory[], sectionCount: number, dataStream: DataStreamBuilder, onWarning?: WriteWarning): StorySubdocument;
18
+ /** Every story subdocument a document carries, in [MS-DOC]'s own concatenation order (footnote, header, comment, endnote -- the same order notes.ts/read.ts count ccp boundaries in), each undefined when the document carries none of that kind. */
19
+ interface StorySubdocuments {
20
+ readonly footnote: StorySubdocument | undefined;
21
+ readonly header: StorySubdocument | undefined;
22
+ readonly comment: StorySubdocument | undefined;
23
+ readonly endnote: StorySubdocument | undefined;
24
+ }
25
+ /** The input type's own optional story fields, isolated here so buildStorySubdocuments states its shape once -- writeDocContent's WritableDocContent satisfies it directly. */
26
+ interface StorySources {
27
+ readonly footnotes?: readonly {
28
+ readonly text: string;
29
+ }[];
30
+ readonly endnotes?: readonly {
31
+ readonly text: string;
32
+ }[];
33
+ readonly comments?: readonly {
34
+ readonly text: string;
35
+ }[];
36
+ readonly headerFooterStories?: readonly HeaderFooterStory[];
37
+ }
38
+ declare function buildStorySubdocuments(document: StorySources & Pick<Extract<ContentDocument, {
39
+ kind: "wordprocessing";
40
+ }>, "sections">, dataStream: DataStreamBuilder, onWarning?: WriteWarning): StorySubdocuments;
41
+ //#endregion
42
+ export { StorySubdocument, StorySubdocuments, buildHeaderSubdocument, buildNoteSubdocument, buildStorySubdocuments, paragraphCharacters };
@@ -0,0 +1,104 @@
1
+ import { DocFormatError } from "./errors.js";
2
+ import "./text/special.js";
3
+ import { SLOT_ORDER } from "./headers-footers.js";
4
+ import { flattenSectionBlocks } from "./table/write.js";
5
+ //#region src/subdocument-write.ts
6
+ /** A plain one-run text run: no character formatting, no extra grpprl -- a note body's own text carries no modelled formatting to state. */
7
+ function textRun(text) {
8
+ return {
9
+ run: { text },
10
+ extraGrpprl: []
11
+ };
12
+ }
13
+ /** The bare paragraph mark every non-empty story ends with -- no runs, no properties, the "not considered part of the story contents" guard itself. */
14
+ function guardParagraph() {
15
+ return {
16
+ runs: [],
17
+ properties: {},
18
+ extraGrpprl: [],
19
+ terminator: 13
20
+ };
21
+ }
22
+ /** One paragraph's own character footprint in the text stream: every run's own characters plus the paragraph's own terminator. Exported because write.ts counts the main document's own ccpText with the identical arithmetic -- one source, so the two can never disagree about what a paragraph costs. */
23
+ function paragraphCharacters(paragraph) {
24
+ return 1 + paragraph.runs.reduce((count, run) => count + run.run.text.length, 0);
25
+ }
26
+ /** A CP-only plex (element size 0, [MS-DOC] 2.2.2's own "the data thus has a size of 0 bytes" shape PlcffndTxt/PlcfandTxt/PlcfendTxt/PlcfHdd all share): just the aCP array, one 4-byte little-endian value per key. */
27
+ function buildCpOnlyPlex(keys) {
28
+ const bytes = new Uint8Array(keys.length * 4);
29
+ const view = new DataView(bytes.buffer);
30
+ keys.forEach((key, index) => {
31
+ view.setUint32(index * 4, key, true);
32
+ });
33
+ return bytes;
34
+ }
35
+ function finishSubdocument(paragraphs, storyStarts, characters) {
36
+ paragraphs.push(guardParagraph());
37
+ const ccp = characters + 1;
38
+ return {
39
+ paragraphs,
40
+ plex: buildCpOnlyPlex([
41
+ ...storyStarts,
42
+ characters,
43
+ ccp
44
+ ]),
45
+ ccp
46
+ };
47
+ }
48
+ function buildNoteSubdocument(notes) {
49
+ const paragraphs = [];
50
+ const storyStarts = [];
51
+ let characters = 0;
52
+ for (const note of notes) {
53
+ storyStarts.push(characters);
54
+ const story = note.text.split("\n").map((line) => ({
55
+ runs: line === "" ? [] : [textRun(line)],
56
+ properties: {},
57
+ extraGrpprl: [],
58
+ terminator: 13
59
+ }));
60
+ story.push(guardParagraph());
61
+ for (const paragraph of story) characters += paragraphCharacters(paragraph);
62
+ paragraphs.push(...story);
63
+ }
64
+ return finishSubdocument(paragraphs, storyStarts, characters);
65
+ }
66
+ function buildHeaderSubdocument(stories, sectionCount, dataStream, onWarning) {
67
+ const bySlot = /* @__PURE__ */ new Map();
68
+ for (const story of stories) {
69
+ if (!Number.isInteger(story.section) || story.section < 0 || story.section >= sectionCount) throw new DocFormatError(`a header/footer story names section ${String(story.section)}, but this document has ${String(sectionCount)} sections`);
70
+ const key = `${String(story.section)}:${story.slot}`;
71
+ if (bySlot.has(key)) throw new DocFormatError(`more than one header/footer story was given for section ${String(story.section)}'s ${story.slot} slot; a slot holds one story`);
72
+ bySlot.set(key, story);
73
+ }
74
+ const paragraphs = [];
75
+ const storyStarts = [];
76
+ let characters = 0;
77
+ for (let count = 0; count < 6; count += 1) storyStarts.push(0);
78
+ for (let section = 0; section < sectionCount; section += 1) for (const slot of SLOT_ORDER) {
79
+ storyStarts.push(characters);
80
+ const story = bySlot.get(`${String(section)}:${slot}`);
81
+ if (story === void 0) continue;
82
+ let content = flattenSectionBlocks(story.blocks, dataStream, onWarning);
83
+ if (content.length === 0) content = [{
84
+ runs: [],
85
+ properties: {},
86
+ extraGrpprl: [],
87
+ terminator: 13
88
+ }];
89
+ content.push(guardParagraph());
90
+ for (const paragraph of content) characters += paragraphCharacters(paragraph);
91
+ paragraphs.push(...content);
92
+ }
93
+ return finishSubdocument(paragraphs, storyStarts, characters);
94
+ }
95
+ function buildStorySubdocuments(document, dataStream, onWarning) {
96
+ return {
97
+ footnote: document.footnotes !== void 0 && document.footnotes.length > 0 ? buildNoteSubdocument(document.footnotes) : void 0,
98
+ header: document.headerFooterStories !== void 0 && document.headerFooterStories.length > 0 ? buildHeaderSubdocument(document.headerFooterStories, document.sections.length, dataStream, onWarning) : void 0,
99
+ comment: document.comments !== void 0 && document.comments.length > 0 ? buildNoteSubdocument(document.comments) : void 0,
100
+ endnote: document.endnotes !== void 0 && document.endnotes.length > 0 ? buildNoteSubdocument(document.endnotes) : void 0
101
+ };
102
+ }
103
+ //#endregion
104
+ export { buildHeaderSubdocument, buildNoteSubdocument, buildStorySubdocuments, paragraphCharacters };
@@ -9,7 +9,14 @@ function readSubdocumentStories(wordDocument, table, pieceTable, context, subdoc
9
9
  const range = require_text_characters.readTextRange(wordDocument, pieceTable, subdocStartCp, subdocStartCp + subdocLength);
10
10
  const entries = require_text_paragraphs.readParagraphs(range.text, range.fcs, context);
11
11
  const plc = require_plc.parsePlc(require_bytes.slice(table, boundaryFc, boundaryLcb, `${what} in the Table stream`), 0, what);
12
- return require_text_paragraphs.splitEntriesByBoundaries(entries, plc.keys).slice(0, -1).map((story) => story.slice(0, -1));
12
+ return require_text_paragraphs.splitEntriesByBoundaries(entries, plc.keys).slice(0, -1).map((story) => endsWithGuardParagraph(story) ? story.slice(0, -1) : story);
13
+ }
14
+ function endsWithGuardParagraph(story) {
15
+ const last = story[story.length - 1];
16
+ if (last === void 0) return false;
17
+ if (last.blocks.length !== 1) return false;
18
+ const only = last.blocks[0];
19
+ return only?.kind === "paragraph" && only.runs.length === 0;
13
20
  }
14
21
  /** Joins a story's own paragraphs into plain text -- one line per paragraph, matching how ooxml.js's own Footnote/Comment reading concatenates a footnote or comment body's `w:t` runs rather than preserving paragraph/run structure. A footnote, endnote, or comment in this family is carried as text alone; only a header or footer keeps real block flow (headers-footers.ts). Non-paragraph blocks a story's own entries produced (an inline picture split out of one of its paragraphs) contribute no text of their own here, the same way an image has no plain-text spelling in ooxml.js's own footnote/comment reading either. */
15
22
  function storyText(entries) {
@@ -8,7 +8,14 @@ function readSubdocumentStories(wordDocument, table, pieceTable, context, subdoc
8
8
  const range = readTextRange(wordDocument, pieceTable, subdocStartCp, subdocStartCp + subdocLength);
9
9
  const entries = readParagraphs(range.text, range.fcs, context);
10
10
  const plc = parsePlc(slice(table, boundaryFc, boundaryLcb, `${what} in the Table stream`), 0, what);
11
- return splitEntriesByBoundaries(entries, plc.keys).slice(0, -1).map((story) => story.slice(0, -1));
11
+ return splitEntriesByBoundaries(entries, plc.keys).slice(0, -1).map((story) => endsWithGuardParagraph(story) ? story.slice(0, -1) : story);
12
+ }
13
+ function endsWithGuardParagraph(story) {
14
+ const last = story[story.length - 1];
15
+ if (last === void 0) return false;
16
+ if (last.blocks.length !== 1) return false;
17
+ const only = last.blocks[0];
18
+ return only?.kind === "paragraph" && only.runs.length === 0;
12
19
  }
13
20
  /** Joins a story's own paragraphs into plain text -- one line per paragraph, matching how ooxml.js's own Footnote/Comment reading concatenates a footnote or comment body's `w:t` runs rather than preserving paragraph/run structure. A footnote, endnote, or comment in this family is carried as text alone; only a header or footer keeps real block flow (headers-footers.ts). Non-paragraph blocks a story's own entries produced (an inline picture split out of one of its paragraphs) contribute no text of their own here, the same way an image has no plain-text spelling in ooxml.js's own footnote/comment reading either. */
14
21
  function storyText(entries) {
@@ -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;
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 };