doc-codec 1.0.1 → 1.1.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 (112) hide show
  1. package/README.md +192 -50
  2. package/dist/color.cjs +166 -0
  3. package/dist/color.d.cts +20 -0
  4. package/dist/color.d.ts +20 -0
  5. package/dist/color.js +160 -0
  6. package/dist/detect.cjs +3 -0
  7. package/dist/detect.d.cts +3 -1
  8. package/dist/detect.d.ts +3 -1
  9. package/dist/detect.js +3 -1
  10. package/dist/fib/fib.cjs +9 -1
  11. package/dist/fib/fib.d.cts +1 -28
  12. package/dist/fib/fib.d.ts +1 -28
  13. package/dist/fib/fib.js +9 -1
  14. package/dist/fib/offsets.cjs +9 -1
  15. package/dist/fib/offsets.d.cts +8 -0
  16. package/dist/fib/offsets.d.ts +8 -0
  17. package/dist/fib/offsets.js +9 -1
  18. package/dist/fib/write.cjs +43 -0
  19. package/dist/fib/write.d.cts +21 -0
  20. package/dist/fib/write.d.ts +21 -0
  21. package/dist/fib/write.js +42 -0
  22. package/dist/fib-CtsLtF96.d.cts +37 -0
  23. package/dist/fib-CtsLtF96.d.ts +37 -0
  24. package/dist/index.cjs +31 -0
  25. package/dist/index.d.cts +18 -7
  26. package/dist/index.d.ts +18 -7
  27. package/dist/index.js +14 -3
  28. package/dist/list/numbering.cjs +206 -0
  29. package/dist/list/numbering.d.cts +2 -0
  30. package/dist/list/numbering.d.ts +2 -0
  31. package/dist/list/numbering.js +205 -0
  32. package/dist/metadata.cjs +14 -0
  33. package/dist/metadata.d.cts +6 -0
  34. package/dist/metadata.d.ts +6 -0
  35. package/dist/metadata.js +13 -0
  36. package/dist/numbering-Cir-uGdc.d.cts +22 -0
  37. package/dist/numbering-DF1z1PyG.d.ts +22 -0
  38. package/dist/pap-BkFo8dZO.d.ts +29 -0
  39. package/dist/pap-BmelEYEP.d.cts +29 -0
  40. package/dist/prop/chp-write.cjs +47 -0
  41. package/dist/prop/chp-write.d.cts +5 -0
  42. package/dist/prop/chp-write.d.ts +5 -0
  43. package/dist/prop/chp-write.js +46 -0
  44. package/dist/prop/chp.cjs +15 -107
  45. package/dist/prop/chp.d.cts +3 -2
  46. package/dist/prop/chp.d.ts +3 -2
  47. package/dist/prop/chp.js +15 -107
  48. package/dist/prop/fkp-write.cjs +139 -0
  49. package/dist/prop/fkp-write.d.cts +21 -0
  50. package/dist/prop/fkp-write.d.ts +21 -0
  51. package/dist/prop/fkp-write.js +135 -0
  52. package/dist/prop/pap-write.cjs +67 -0
  53. package/dist/prop/pap-write.d.cts +5 -0
  54. package/dist/prop/pap-write.d.ts +5 -0
  55. package/dist/prop/pap-write.js +66 -0
  56. package/dist/prop/pap.cjs +10 -0
  57. package/dist/prop/pap.d.cts +1 -24
  58. package/dist/prop/pap.d.ts +1 -24
  59. package/dist/prop/pap.js +11 -1
  60. package/dist/prop/sep-write.cjs +63 -0
  61. package/dist/prop/sep-write.d.cts +9 -0
  62. package/dist/prop/sep-write.d.ts +9 -0
  63. package/dist/prop/sep-write.js +60 -0
  64. package/dist/prop/sep.cjs +63 -0
  65. package/dist/prop/sep.d.cts +16 -0
  66. package/dist/prop/sep.d.ts +16 -0
  67. package/dist/prop/sep.js +61 -0
  68. package/dist/prop/sprm.d.cts +1 -25
  69. package/dist/prop/sprm.d.ts +1 -25
  70. package/dist/read.cjs +44 -18
  71. package/dist/read.d.cts +21 -4
  72. package/dist/read.d.ts +21 -4
  73. package/dist/read.js +46 -20
  74. package/dist/sprm-Djg5RNiq.d.cts +26 -0
  75. package/dist/sprm-Djg5RNiq.d.ts +26 -0
  76. package/dist/style/fonts.cjs +68 -0
  77. package/dist/style/fonts.d.cts +5 -0
  78. package/dist/style/fonts.d.ts +5 -0
  79. package/dist/style/fonts.js +66 -0
  80. package/dist/style/stsh.cjs +23 -0
  81. package/dist/style/stsh.d.cts +2 -1
  82. package/dist/style/stsh.d.ts +2 -1
  83. package/dist/style/stsh.js +23 -1
  84. package/dist/table/decoration.cjs +220 -0
  85. package/dist/table/decoration.d.cts +41 -0
  86. package/dist/table/decoration.d.ts +41 -0
  87. package/dist/table/decoration.js +205 -0
  88. package/dist/table/read.cjs +192 -0
  89. package/dist/table/read.d.cts +6 -0
  90. package/dist/table/read.d.ts +6 -0
  91. package/dist/table/read.js +191 -0
  92. package/dist/table/tap-write.cjs +125 -0
  93. package/dist/table/tap-write.d.cts +13 -0
  94. package/dist/table/tap-write.d.ts +13 -0
  95. package/dist/table/tap-write.js +124 -0
  96. package/dist/table/tap.cjs +217 -0
  97. package/dist/table/tap.d.cts +29 -0
  98. package/dist/table/tap.d.ts +29 -0
  99. package/dist/table/tap.js +213 -0
  100. package/dist/table/write.cjs +136 -0
  101. package/dist/table/write.d.cts +13 -0
  102. package/dist/table/write.d.ts +13 -0
  103. package/dist/table/write.js +135 -0
  104. package/dist/text/piece-table-write.cjs +24 -0
  105. package/dist/text/piece-table-write.d.cts +4 -0
  106. package/dist/text/piece-table-write.d.ts +4 -0
  107. package/dist/text/piece-table-write.js +23 -0
  108. package/dist/write.cjs +175 -0
  109. package/dist/write.d.cts +5 -0
  110. package/dist/write.d.ts +5 -0
  111. package/dist/write.js +174 -0
  112. package/package.json +3 -3
@@ -0,0 +1,135 @@
1
+ import { DocFormatError, DocUnsupportedError } from "../errors.js";
2
+ import "../text/special.js";
3
+ import { encodeTableRowGrpprl } from "./tap-write.js";
4
+ //#region src/table/write.ts
5
+ /** sprmPFInTable (0x2416): a Bool8, "MUST be 1 any time the table depth is greater than zero". */
6
+ const SPRM_P_F_IN_TABLE = 9238;
7
+ /** sprmPFTtp (0x2417): a Bool8 marking a cell mark as the row's own Table Terminating Paragraph mark. */
8
+ const SPRM_P_F_TTP = 9239;
9
+ const TWIPS_PER_POINT = 20;
10
+ function pushSprm(bytes, opcode, operand) {
11
+ bytes.push(opcode & 255, opcode >> 8 & 255, ...operand);
12
+ }
13
+ function inTableGrpprl() {
14
+ const bytes = [];
15
+ pushSprm(bytes, SPRM_P_F_IN_TABLE, [1]);
16
+ return bytes;
17
+ }
18
+ function rowMarkExtraGrpprl(boundaries, cellsToWrite, heightPt) {
19
+ const bytes = inTableGrpprl();
20
+ pushSprm(bytes, SPRM_P_F_TTP, [1]);
21
+ bytes.push(...encodeTableRowGrpprl(boundaries, cellsToWrite, heightPt));
22
+ return bytes;
23
+ }
24
+ function cellParagraphs(blocks) {
25
+ if (blocks.length === 0) return [{
26
+ runs: [],
27
+ properties: {},
28
+ extraGrpprl: inTableGrpprl(),
29
+ terminator: 7
30
+ }];
31
+ return blocks.map((block, index) => {
32
+ 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)`);
33
+ return {
34
+ runs: block.runs,
35
+ properties: block,
36
+ extraGrpprl: inTableGrpprl(),
37
+ terminator: index === blocks.length - 1 ? 7 : 13
38
+ };
39
+ });
40
+ }
41
+ function columnBoundariesTwips(columnWidthsPt) {
42
+ const boundaries = [0];
43
+ let cumulative = 0;
44
+ for (const widthPt of columnWidthsPt) {
45
+ cumulative += widthPt * TWIPS_PER_POINT;
46
+ boundaries.push(cumulative);
47
+ }
48
+ return boundaries;
49
+ }
50
+ function flattenRow(cells, columnCount, boundaries, active) {
51
+ const paragraphs = [];
52
+ const cellsToWrite = [];
53
+ const firstBoundary = boundaries[0];
54
+ if (firstBoundary === void 0) throw new DocFormatError("internal defect: a table's own column-boundary array is empty despite the columnCount guard above");
55
+ const rowBoundariesTwips = [firstBoundary];
56
+ let column = 0;
57
+ for (const cell of cells) {
58
+ const covered = active.get(column);
59
+ const isContinuation = cell.blocks.length === 0 && covered !== void 0 && covered.remaining > 0;
60
+ let span;
61
+ let vertMerge;
62
+ let blocks;
63
+ if (isContinuation) {
64
+ covered.remaining -= 1;
65
+ span = covered.span;
66
+ vertMerge = 1;
67
+ blocks = [];
68
+ } else {
69
+ span = cell.colSpan ?? 1;
70
+ const rowSpan = cell.rowSpan ?? 1;
71
+ vertMerge = rowSpan > 1 ? 3 : 0;
72
+ if (rowSpan > 1) active.set(column, {
73
+ span,
74
+ remaining: rowSpan - 1
75
+ });
76
+ blocks = cell.blocks;
77
+ }
78
+ paragraphs.push(...cellParagraphs(blocks));
79
+ cellsToWrite.push(isContinuation ? { vertMerge } : {
80
+ vertMerge,
81
+ borders: cell.borders,
82
+ background: cell.background
83
+ });
84
+ column += span;
85
+ const rightBoundary = boundaries[column];
86
+ if (rightBoundary === void 0) throw new DocFormatError(`a table cell's own colSpan runs past the table's ${columnCount}-column grid`);
87
+ rowBoundariesTwips.push(rightBoundary);
88
+ }
89
+ if (column !== columnCount) throw new DocFormatError(`a table row's own cells cover ${column} columns (via colSpan), but the table declares ${columnCount} in columnWidthsPt`);
90
+ return {
91
+ paragraphs,
92
+ cellsToWrite,
93
+ rowBoundariesTwips
94
+ };
95
+ }
96
+ function flattenTable(table) {
97
+ const columnCount = table.columnWidthsPt.length;
98
+ if (columnCount === 0 || table.rows.length === 0) throw new DocFormatError("a table must have at least one column and one row to write");
99
+ const boundaries = columnBoundariesTwips(table.columnWidthsPt);
100
+ const active = /* @__PURE__ */ new Map();
101
+ const output = [];
102
+ for (const row of table.rows) {
103
+ const { paragraphs, cellsToWrite, rowBoundariesTwips } = flattenRow(row.cells, columnCount, boundaries, active);
104
+ output.push(...paragraphs);
105
+ output.push({
106
+ runs: [],
107
+ properties: {},
108
+ extraGrpprl: rowMarkExtraGrpprl(rowBoundariesTwips, cellsToWrite, row.heightPt),
109
+ terminator: 7
110
+ });
111
+ }
112
+ return output;
113
+ }
114
+ function flattenSectionBlocks(blocks) {
115
+ const output = [];
116
+ for (const block of blocks) {
117
+ if (block.kind === "paragraph") {
118
+ output.push({
119
+ runs: block.runs,
120
+ properties: block,
121
+ extraGrpprl: [],
122
+ terminator: 13
123
+ });
124
+ continue;
125
+ }
126
+ if (block.kind === "table") {
127
+ output.push(...flattenTable(block));
128
+ continue;
129
+ }
130
+ throw new DocUnsupportedError(`doc-codec's writer does not yet support '${block.kind}' blocks (see README's scope note)`);
131
+ }
132
+ return output;
133
+ }
134
+ //#endregion
135
+ export { flattenSectionBlocks };
@@ -0,0 +1,24 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
+ //#region src/text/piece-table-write.ts
4
+ /** Pcdt's own marker byte, [MS-DOC] 2.9.19. */
5
+ const CLXT_PCDT = 2;
6
+ function push32(bytes, value) {
7
+ bytes.push(value & 255, value >> 8 & 255, value >> 16 & 255, value >>> 24 & 255);
8
+ }
9
+ function buildTextClx(characterCount, textFc) {
10
+ if (!Number.isInteger(characterCount) || characterCount < 1) throw new require_errors.DocFormatError(`a Clx must cover at least one character position (the mandatory closing paragraph mark); got characterCount ${characterCount}`);
11
+ const plcPcd = [];
12
+ push32(plcPcd, 0);
13
+ push32(plcPcd, characterCount);
14
+ plcPcd.push(0, 0);
15
+ push32(plcPcd, textFc);
16
+ plcPcd.push(0, 0);
17
+ if ((plcPcd.length - 4) / 12 !== 1) throw new require_errors.DocFormatError("buildTextClx's own PlcPcd does not describe exactly one piece; this is an internal defect, not an input error");
18
+ const clx = [CLXT_PCDT];
19
+ push32(clx, plcPcd.length);
20
+ clx.push(...plcPcd);
21
+ return new Uint8Array(clx);
22
+ }
23
+ //#endregion
24
+ exports.buildTextClx = buildTextClx;
@@ -0,0 +1,4 @@
1
+ //#region src/text/piece-table-write.d.ts
2
+ declare function buildTextClx(characterCount: number, textFc: number): Uint8Array;
3
+ //#endregion
4
+ export { buildTextClx };
@@ -0,0 +1,4 @@
1
+ //#region src/text/piece-table-write.d.ts
2
+ declare function buildTextClx(characterCount: number, textFc: number): Uint8Array;
3
+ //#endregion
4
+ export { buildTextClx };
@@ -0,0 +1,23 @@
1
+ import { DocFormatError } from "../errors.js";
2
+ //#region src/text/piece-table-write.ts
3
+ /** Pcdt's own marker byte, [MS-DOC] 2.9.19. */
4
+ const CLXT_PCDT = 2;
5
+ function push32(bytes, value) {
6
+ bytes.push(value & 255, value >> 8 & 255, value >> 16 & 255, value >>> 24 & 255);
7
+ }
8
+ function buildTextClx(characterCount, textFc) {
9
+ if (!Number.isInteger(characterCount) || characterCount < 1) throw new DocFormatError(`a Clx must cover at least one character position (the mandatory closing paragraph mark); got characterCount ${characterCount}`);
10
+ const plcPcd = [];
11
+ push32(plcPcd, 0);
12
+ push32(plcPcd, characterCount);
13
+ plcPcd.push(0, 0);
14
+ push32(plcPcd, textFc);
15
+ plcPcd.push(0, 0);
16
+ if ((plcPcd.length - 4) / 12 !== 1) throw new DocFormatError("buildTextClx's own PlcPcd does not describe exactly one piece; this is an internal defect, not an input error");
17
+ const clx = [CLXT_PCDT];
18
+ push32(clx, plcPcd.length);
19
+ clx.push(...plcPcd);
20
+ return new Uint8Array(clx);
21
+ }
22
+ //#endregion
23
+ export { buildTextClx };
package/dist/write.cjs ADDED
@@ -0,0 +1,175 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("./errors.cjs");
3
+ const require_detect = require("./detect.cjs");
4
+ const require_fib_write = require("./fib/write.cjs");
5
+ const require_metadata = require("./metadata.cjs");
6
+ const require_text_piece_table_write = require("./text/piece-table-write.cjs");
7
+ require("./text/special.cjs");
8
+ require("./prop/fkp.cjs");
9
+ const require_prop_fkp_write = require("./prop/fkp-write.cjs");
10
+ const require_prop_chp_write = require("./prop/chp-write.cjs");
11
+ const require_prop_pap_write = require("./prop/pap-write.cjs");
12
+ const require_prop_sep_write = require("./prop/sep-write.cjs");
13
+ const require_style_stsh = require("./style/stsh.cjs");
14
+ const require_style_fonts = require("./style/fonts.cjs");
15
+ const require_table_write = require("./table/write.cjs");
16
+ let archive_codec = require("archive-codec");
17
+ //#region src/write.ts
18
+ /** 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. */
19
+ const TEXT_FC = 1024;
20
+ /** This writer only ever emits 16-bit (uncompressed) text -- see text/piece-table-write.ts. */
21
+ const BYTES_PER_CHARACTER = 2;
22
+ function writeDocContent(document) {
23
+ if (document.kind !== "wordprocessing") throw new require_errors.DocUnsupportedError(`doc-codec writes wordprocessing documents only; got a '${document.kind}' document`);
24
+ 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`);
25
+ const [section] = document.sections;
26
+ if (section === void 0) throw new require_errors.DocFormatError("a wordprocessing document must carry a section");
27
+ const writeParagraphs = require_table_write.flattenSectionBlocks(section.blocks);
28
+ if (writeParagraphs[writeParagraphs.length - 1]?.terminator !== 13) writeParagraphs.push({
29
+ runs: [],
30
+ properties: {},
31
+ extraGrpprl: [],
32
+ terminator: 13
33
+ });
34
+ const fontNames = [];
35
+ const fontIndexByName = /* @__PURE__ */ new Map();
36
+ const fontIndexOf = (name) => {
37
+ const existing = fontIndexByName.get(name);
38
+ if (existing !== void 0) return existing;
39
+ const index = fontNames.length;
40
+ fontNames.push(name);
41
+ fontIndexByName.set(name, index);
42
+ return index;
43
+ };
44
+ const formatted = writeParagraphs.map((entry) => ({
45
+ runs: entry.runs.map((run) => ({
46
+ text: run.text,
47
+ grpprl: require_prop_chp_write.encodeCharacterGrpprl(run, fontIndexOf)
48
+ })),
49
+ grpprl: [...require_prop_pap_write.encodeParagraphGrpprl(entry.properties), ...entry.extraGrpprl]
50
+ }));
51
+ let text = "";
52
+ const paragraphStarts = [];
53
+ const chpxRuns = [];
54
+ formatted.forEach((paragraph, paragraphIndex) => {
55
+ paragraphStarts.push(text.length);
56
+ for (const run of paragraph.runs) {
57
+ const runStart = text.length;
58
+ text += run.text;
59
+ if (text.length > runStart) chpxRuns.push({
60
+ start: runStart,
61
+ end: text.length,
62
+ grpprl: run.grpprl.length > 0 ? run.grpprl : void 0
63
+ });
64
+ }
65
+ const terminator = writeParagraphs[paragraphIndex]?.terminator ?? 13;
66
+ text += String.fromCharCode(terminator);
67
+ const lastRun = chpxRuns[chpxRuns.length - 1];
68
+ if (lastRun?.end === text.length - 1) lastRun.end = text.length;
69
+ else chpxRuns.push({
70
+ start: text.length - 1,
71
+ end: text.length,
72
+ grpprl: void 0
73
+ });
74
+ });
75
+ const mergedChpxRuns = [];
76
+ for (const run of chpxRuns) {
77
+ const previous = mergedChpxRuns[mergedChpxRuns.length - 1];
78
+ if (previous?.end === run.start && sameGrpprl(previous.grpprl, run.grpprl)) {
79
+ previous.end = run.end;
80
+ continue;
81
+ }
82
+ mergedChpxRuns.push({ ...run });
83
+ }
84
+ const characterFc = (cp) => TEXT_FC + cp * BYTES_PER_CHARACTER;
85
+ const textFcLim = characterFc(text.length);
86
+ const chpxPageStart = Math.ceil(textFcLim / 512);
87
+ const chpxRunSpecs = mergedChpxRuns.map((run) => ({
88
+ fc: characterFc(run.start),
89
+ grpprl: run.grpprl
90
+ }));
91
+ const chpxPages = require_prop_fkp_write.buildChpxPages(chpxRunSpecs, textFcLim);
92
+ const papxPageStart = chpxPageStart + chpxPages.length;
93
+ const papxParagraphSpecs = formatted.map((paragraph, index) => {
94
+ const start = paragraphStarts[index];
95
+ if (start === void 0) throw new require_errors.DocFormatError("internal defect: writeDocContent lost a paragraph's own start position");
96
+ return {
97
+ fc: characterFc(start),
98
+ istd: 0,
99
+ grpprl: paragraph.grpprl
100
+ };
101
+ });
102
+ const papxPages = require_prop_fkp_write.buildPapxPages(papxParagraphSpecs, textFcLim);
103
+ const sepx = require_prop_sep_write.buildSepx(require_prop_sep_write.encodeSectionGrpprl(section));
104
+ const fcSepx = (papxPageStart + papxPages.length) * 512;
105
+ const wordDocument = new Uint8Array(fcSepx + sepx.length);
106
+ const wordView = new DataView(wordDocument.buffer);
107
+ for (let index = 0; index < text.length; index += 1) wordView.setUint16(characterFc(index), text.charCodeAt(index), true);
108
+ chpxPages.forEach((page, index) => {
109
+ wordDocument.set(page, (chpxPageStart + index) * 512);
110
+ });
111
+ papxPages.forEach((page, index) => {
112
+ wordDocument.set(page, (papxPageStart + index) * 512);
113
+ });
114
+ wordDocument.set(sepx, fcSepx);
115
+ const clx = require_text_piece_table_write.buildTextClx(text.length, TEXT_FC);
116
+ const chpxBinTable = require_prop_fkp_write.buildPropertyBinTable([...chpxPages.map(require_prop_fkp_write.firstFcOfPage), textFcLim], chpxPages.map((_, index) => chpxPageStart + index));
117
+ const papxBinTable = require_prop_fkp_write.buildPropertyBinTable([...papxPages.map(require_prop_fkp_write.firstFcOfPage), textFcLim], papxPages.map((_, index) => papxPageStart + index));
118
+ const stsh = require_style_stsh.buildEmptyStsh();
119
+ const fontTable = fontNames.length > 0 ? require_style_fonts.buildFontTable(fontNames) : void 0;
120
+ const plcfSed = require_prop_sep_write.buildPlcfSed(text.length, fcSepx);
121
+ let cursor = 0;
122
+ const place = (bytes) => {
123
+ const offset = cursor;
124
+ cursor += bytes.length;
125
+ return offset;
126
+ };
127
+ const fcClx = place(clx);
128
+ const fcPlcfBteChpx = place(chpxBinTable);
129
+ const fcPlcfBtePapx = place(papxBinTable);
130
+ const fcStshf = place(stsh);
131
+ const fcPlcfSed = place(plcfSed);
132
+ const fcSttbfFfn = fontTable !== void 0 ? place(fontTable) : 0;
133
+ const table = new Uint8Array(cursor);
134
+ table.set(clx, fcClx);
135
+ table.set(chpxBinTable, fcPlcfBteChpx);
136
+ table.set(papxBinTable, fcPlcfBtePapx);
137
+ table.set(stsh, fcStshf);
138
+ table.set(plcfSed, fcPlcfSed);
139
+ if (fontTable !== void 0) table.set(fontTable, fcSttbfFfn);
140
+ const fib = require_fib_write.buildFib({
141
+ ccpText: text.length,
142
+ cbMac: wordDocument.length,
143
+ fcClx,
144
+ lcbClx: clx.length,
145
+ fcPlcfSed,
146
+ lcbPlcfSed: plcfSed.length,
147
+ fcPlcfBteChpx,
148
+ lcbPlcfBteChpx: chpxBinTable.length,
149
+ fcPlcfBtePapx,
150
+ lcbPlcfBtePapx: papxBinTable.length,
151
+ fcStshf,
152
+ lcbStshf: stsh.length,
153
+ fcSttbfFfn,
154
+ lcbSttbfFfn: fontTable?.length ?? 0
155
+ });
156
+ wordDocument.set(fib, 0);
157
+ const streams = [{
158
+ path: require_detect.WORD_DOCUMENT_STREAM,
159
+ bytes: wordDocument
160
+ }, {
161
+ path: "1Table",
162
+ bytes: table
163
+ }];
164
+ if ((0, archive_codec.hasSummaryInformationFields)(document.metadata)) streams.push({
165
+ path: require_detect.SUMMARY_INFORMATION_STREAM,
166
+ bytes: (0, archive_codec.writeSummaryInformationStream)(require_metadata.layoutMetadataToSummaryInformation(document.metadata))
167
+ });
168
+ return (0, archive_codec.writeCompoundFile)(streams);
169
+ }
170
+ function sameGrpprl(a, b) {
171
+ if (a === void 0 || b === void 0) return a === b;
172
+ return a.length === b.length && a.every((byte, index) => byte === b[index]);
173
+ }
174
+ //#endregion
175
+ exports.writeDocContent = writeDocContent;
@@ -0,0 +1,5 @@
1
+ import { ContentDocument } from "document-schema.js";
2
+ //#region src/write.d.ts
3
+ declare function writeDocContent(document: ContentDocument): Uint8Array<ArrayBuffer>;
4
+ //#endregion
5
+ export { writeDocContent };
@@ -0,0 +1,5 @@
1
+ import { ContentDocument } from "document-schema.js";
2
+ //#region src/write.d.ts
3
+ declare function writeDocContent(document: ContentDocument): Uint8Array<ArrayBuffer>;
4
+ //#endregion
5
+ export { writeDocContent };
package/dist/write.js ADDED
@@ -0,0 +1,174 @@
1
+ import { DocFormatError, DocUnsupportedError } from "./errors.js";
2
+ import { SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM } from "./detect.js";
3
+ import { buildFib } from "./fib/write.js";
4
+ import { layoutMetadataToSummaryInformation as layoutMetadataToSummaryInformation$1 } from "./metadata.js";
5
+ import { buildTextClx } from "./text/piece-table-write.js";
6
+ import "./text/special.js";
7
+ import "./prop/fkp.js";
8
+ import { buildChpxPages, buildPapxPages, buildPropertyBinTable, firstFcOfPage } from "./prop/fkp-write.js";
9
+ import { encodeCharacterGrpprl } from "./prop/chp-write.js";
10
+ import { encodeParagraphGrpprl } from "./prop/pap-write.js";
11
+ import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.js";
12
+ import { buildEmptyStsh } from "./style/stsh.js";
13
+ import { buildFontTable } from "./style/fonts.js";
14
+ import { flattenSectionBlocks } from "./table/write.js";
15
+ import { hasSummaryInformationFields, writeCompoundFile, writeSummaryInformationStream } from "archive-codec";
16
+ //#region src/write.ts
17
+ /** 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. */
18
+ const TEXT_FC = 1024;
19
+ /** This writer only ever emits 16-bit (uncompressed) text -- see text/piece-table-write.ts. */
20
+ const BYTES_PER_CHARACTER = 2;
21
+ function writeDocContent(document) {
22
+ if (document.kind !== "wordprocessing") throw new DocUnsupportedError(`doc-codec writes wordprocessing documents only; got a '${document.kind}' document`);
23
+ 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`);
24
+ const [section] = document.sections;
25
+ if (section === void 0) throw new DocFormatError("a wordprocessing document must carry a section");
26
+ const writeParagraphs = flattenSectionBlocks(section.blocks);
27
+ if (writeParagraphs[writeParagraphs.length - 1]?.terminator !== 13) writeParagraphs.push({
28
+ runs: [],
29
+ properties: {},
30
+ extraGrpprl: [],
31
+ terminator: 13
32
+ });
33
+ const fontNames = [];
34
+ const fontIndexByName = /* @__PURE__ */ new Map();
35
+ const fontIndexOf = (name) => {
36
+ const existing = fontIndexByName.get(name);
37
+ if (existing !== void 0) return existing;
38
+ const index = fontNames.length;
39
+ fontNames.push(name);
40
+ fontIndexByName.set(name, index);
41
+ return index;
42
+ };
43
+ const formatted = writeParagraphs.map((entry) => ({
44
+ runs: entry.runs.map((run) => ({
45
+ text: run.text,
46
+ grpprl: encodeCharacterGrpprl(run, fontIndexOf)
47
+ })),
48
+ grpprl: [...encodeParagraphGrpprl(entry.properties), ...entry.extraGrpprl]
49
+ }));
50
+ let text = "";
51
+ const paragraphStarts = [];
52
+ const chpxRuns = [];
53
+ formatted.forEach((paragraph, paragraphIndex) => {
54
+ paragraphStarts.push(text.length);
55
+ for (const run of paragraph.runs) {
56
+ const runStart = text.length;
57
+ text += run.text;
58
+ if (text.length > runStart) chpxRuns.push({
59
+ start: runStart,
60
+ end: text.length,
61
+ grpprl: run.grpprl.length > 0 ? run.grpprl : void 0
62
+ });
63
+ }
64
+ const terminator = writeParagraphs[paragraphIndex]?.terminator ?? 13;
65
+ text += String.fromCharCode(terminator);
66
+ const lastRun = chpxRuns[chpxRuns.length - 1];
67
+ if (lastRun?.end === text.length - 1) lastRun.end = text.length;
68
+ else chpxRuns.push({
69
+ start: text.length - 1,
70
+ end: text.length,
71
+ grpprl: void 0
72
+ });
73
+ });
74
+ const mergedChpxRuns = [];
75
+ for (const run of chpxRuns) {
76
+ const previous = mergedChpxRuns[mergedChpxRuns.length - 1];
77
+ if (previous?.end === run.start && sameGrpprl(previous.grpprl, run.grpprl)) {
78
+ previous.end = run.end;
79
+ continue;
80
+ }
81
+ mergedChpxRuns.push({ ...run });
82
+ }
83
+ const characterFc = (cp) => TEXT_FC + cp * BYTES_PER_CHARACTER;
84
+ const textFcLim = characterFc(text.length);
85
+ const chpxPageStart = Math.ceil(textFcLim / 512);
86
+ const chpxRunSpecs = mergedChpxRuns.map((run) => ({
87
+ fc: characterFc(run.start),
88
+ grpprl: run.grpprl
89
+ }));
90
+ const chpxPages = buildChpxPages(chpxRunSpecs, textFcLim);
91
+ const papxPageStart = chpxPageStart + chpxPages.length;
92
+ const papxParagraphSpecs = formatted.map((paragraph, index) => {
93
+ const start = paragraphStarts[index];
94
+ if (start === void 0) throw new DocFormatError("internal defect: writeDocContent lost a paragraph's own start position");
95
+ return {
96
+ fc: characterFc(start),
97
+ istd: 0,
98
+ grpprl: paragraph.grpprl
99
+ };
100
+ });
101
+ const papxPages = buildPapxPages(papxParagraphSpecs, textFcLim);
102
+ const sepx = buildSepx(encodeSectionGrpprl(section));
103
+ const fcSepx = (papxPageStart + papxPages.length) * 512;
104
+ const wordDocument = new Uint8Array(fcSepx + sepx.length);
105
+ const wordView = new DataView(wordDocument.buffer);
106
+ for (let index = 0; index < text.length; index += 1) wordView.setUint16(characterFc(index), text.charCodeAt(index), true);
107
+ chpxPages.forEach((page, index) => {
108
+ wordDocument.set(page, (chpxPageStart + index) * 512);
109
+ });
110
+ papxPages.forEach((page, index) => {
111
+ wordDocument.set(page, (papxPageStart + index) * 512);
112
+ });
113
+ wordDocument.set(sepx, fcSepx);
114
+ const clx = buildTextClx(text.length, TEXT_FC);
115
+ const chpxBinTable = buildPropertyBinTable([...chpxPages.map(firstFcOfPage), textFcLim], chpxPages.map((_, index) => chpxPageStart + index));
116
+ const papxBinTable = buildPropertyBinTable([...papxPages.map(firstFcOfPage), textFcLim], papxPages.map((_, index) => papxPageStart + index));
117
+ const stsh = buildEmptyStsh();
118
+ const fontTable = fontNames.length > 0 ? buildFontTable(fontNames) : void 0;
119
+ const plcfSed = buildPlcfSed(text.length, fcSepx);
120
+ let cursor = 0;
121
+ const place = (bytes) => {
122
+ const offset = cursor;
123
+ cursor += bytes.length;
124
+ return offset;
125
+ };
126
+ const fcClx = place(clx);
127
+ const fcPlcfBteChpx = place(chpxBinTable);
128
+ const fcPlcfBtePapx = place(papxBinTable);
129
+ const fcStshf = place(stsh);
130
+ const fcPlcfSed = place(plcfSed);
131
+ const fcSttbfFfn = fontTable !== void 0 ? place(fontTable) : 0;
132
+ const table = new Uint8Array(cursor);
133
+ table.set(clx, fcClx);
134
+ table.set(chpxBinTable, fcPlcfBteChpx);
135
+ table.set(papxBinTable, fcPlcfBtePapx);
136
+ table.set(stsh, fcStshf);
137
+ table.set(plcfSed, fcPlcfSed);
138
+ if (fontTable !== void 0) table.set(fontTable, fcSttbfFfn);
139
+ const fib = buildFib({
140
+ ccpText: text.length,
141
+ cbMac: wordDocument.length,
142
+ fcClx,
143
+ lcbClx: clx.length,
144
+ fcPlcfSed,
145
+ lcbPlcfSed: plcfSed.length,
146
+ fcPlcfBteChpx,
147
+ lcbPlcfBteChpx: chpxBinTable.length,
148
+ fcPlcfBtePapx,
149
+ lcbPlcfBtePapx: papxBinTable.length,
150
+ fcStshf,
151
+ lcbStshf: stsh.length,
152
+ fcSttbfFfn,
153
+ lcbSttbfFfn: fontTable?.length ?? 0
154
+ });
155
+ wordDocument.set(fib, 0);
156
+ const streams = [{
157
+ path: WORD_DOCUMENT_STREAM,
158
+ bytes: wordDocument
159
+ }, {
160
+ path: "1Table",
161
+ bytes: table
162
+ }];
163
+ if (hasSummaryInformationFields(document.metadata)) streams.push({
164
+ path: SUMMARY_INFORMATION_STREAM,
165
+ bytes: writeSummaryInformationStream(layoutMetadataToSummaryInformation$1(document.metadata))
166
+ });
167
+ return writeCompoundFile(streams);
168
+ }
169
+ function sameGrpprl(a, b) {
170
+ if (a === void 0 || b === void 0) return a === b;
171
+ return a.length === b.length && a.every((byte, index) => byte === b[index]);
172
+ }
173
+ //#endregion
174
+ export { writeDocContent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-codec",
3
- "version": "1.0.1",
3
+ "version": "1.1.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": {
@@ -75,8 +75,8 @@
75
75
  "license": "MIT",
76
76
  "packageManager": "pnpm@11.6.0",
77
77
  "dependencies": {
78
- "archive-codec": "^1.3.0",
79
- "document-schema.js": "^5.4.0"
78
+ "archive-codec": "^1.4.0",
79
+ "document-schema.js": "^5.5.1"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@arethetypeswrong/cli": "^0.18.5",