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.
- package/README.md +192 -50
- package/dist/color.cjs +166 -0
- package/dist/color.d.cts +20 -0
- package/dist/color.d.ts +20 -0
- package/dist/color.js +160 -0
- package/dist/detect.cjs +3 -0
- package/dist/detect.d.cts +3 -1
- package/dist/detect.d.ts +3 -1
- package/dist/detect.js +3 -1
- package/dist/fib/fib.cjs +9 -1
- package/dist/fib/fib.d.cts +1 -28
- package/dist/fib/fib.d.ts +1 -28
- package/dist/fib/fib.js +9 -1
- package/dist/fib/offsets.cjs +9 -1
- package/dist/fib/offsets.d.cts +8 -0
- package/dist/fib/offsets.d.ts +8 -0
- package/dist/fib/offsets.js +9 -1
- package/dist/fib/write.cjs +43 -0
- package/dist/fib/write.d.cts +21 -0
- package/dist/fib/write.d.ts +21 -0
- package/dist/fib/write.js +42 -0
- package/dist/fib-CtsLtF96.d.cts +37 -0
- package/dist/fib-CtsLtF96.d.ts +37 -0
- package/dist/index.cjs +31 -0
- package/dist/index.d.cts +18 -7
- package/dist/index.d.ts +18 -7
- package/dist/index.js +14 -3
- package/dist/list/numbering.cjs +206 -0
- package/dist/list/numbering.d.cts +2 -0
- package/dist/list/numbering.d.ts +2 -0
- package/dist/list/numbering.js +205 -0
- package/dist/metadata.cjs +14 -0
- package/dist/metadata.d.cts +6 -0
- package/dist/metadata.d.ts +6 -0
- package/dist/metadata.js +13 -0
- package/dist/numbering-Cir-uGdc.d.cts +22 -0
- package/dist/numbering-DF1z1PyG.d.ts +22 -0
- package/dist/pap-BkFo8dZO.d.ts +29 -0
- package/dist/pap-BmelEYEP.d.cts +29 -0
- package/dist/prop/chp-write.cjs +47 -0
- package/dist/prop/chp-write.d.cts +5 -0
- package/dist/prop/chp-write.d.ts +5 -0
- package/dist/prop/chp-write.js +46 -0
- package/dist/prop/chp.cjs +15 -107
- package/dist/prop/chp.d.cts +3 -2
- package/dist/prop/chp.d.ts +3 -2
- package/dist/prop/chp.js +15 -107
- package/dist/prop/fkp-write.cjs +139 -0
- package/dist/prop/fkp-write.d.cts +21 -0
- package/dist/prop/fkp-write.d.ts +21 -0
- package/dist/prop/fkp-write.js +135 -0
- package/dist/prop/pap-write.cjs +67 -0
- package/dist/prop/pap-write.d.cts +5 -0
- package/dist/prop/pap-write.d.ts +5 -0
- package/dist/prop/pap-write.js +66 -0
- package/dist/prop/pap.cjs +10 -0
- package/dist/prop/pap.d.cts +1 -24
- package/dist/prop/pap.d.ts +1 -24
- package/dist/prop/pap.js +11 -1
- package/dist/prop/sep-write.cjs +63 -0
- package/dist/prop/sep-write.d.cts +9 -0
- package/dist/prop/sep-write.d.ts +9 -0
- package/dist/prop/sep-write.js +60 -0
- package/dist/prop/sep.cjs +63 -0
- package/dist/prop/sep.d.cts +16 -0
- package/dist/prop/sep.d.ts +16 -0
- package/dist/prop/sep.js +61 -0
- package/dist/prop/sprm.d.cts +1 -25
- package/dist/prop/sprm.d.ts +1 -25
- package/dist/read.cjs +44 -18
- package/dist/read.d.cts +21 -4
- package/dist/read.d.ts +21 -4
- package/dist/read.js +46 -20
- package/dist/sprm-Djg5RNiq.d.cts +26 -0
- package/dist/sprm-Djg5RNiq.d.ts +26 -0
- package/dist/style/fonts.cjs +68 -0
- package/dist/style/fonts.d.cts +5 -0
- package/dist/style/fonts.d.ts +5 -0
- package/dist/style/fonts.js +66 -0
- package/dist/style/stsh.cjs +23 -0
- package/dist/style/stsh.d.cts +2 -1
- package/dist/style/stsh.d.ts +2 -1
- package/dist/style/stsh.js +23 -1
- package/dist/table/decoration.cjs +220 -0
- package/dist/table/decoration.d.cts +41 -0
- package/dist/table/decoration.d.ts +41 -0
- package/dist/table/decoration.js +205 -0
- package/dist/table/read.cjs +192 -0
- package/dist/table/read.d.cts +6 -0
- package/dist/table/read.d.ts +6 -0
- package/dist/table/read.js +191 -0
- package/dist/table/tap-write.cjs +125 -0
- package/dist/table/tap-write.d.cts +13 -0
- package/dist/table/tap-write.d.ts +13 -0
- package/dist/table/tap-write.js +124 -0
- package/dist/table/tap.cjs +217 -0
- package/dist/table/tap.d.cts +29 -0
- package/dist/table/tap.d.ts +29 -0
- package/dist/table/tap.js +213 -0
- package/dist/table/write.cjs +136 -0
- package/dist/table/write.d.cts +13 -0
- package/dist/table/write.d.ts +13 -0
- package/dist/table/write.js +135 -0
- package/dist/text/piece-table-write.cjs +24 -0
- package/dist/text/piece-table-write.d.cts +4 -0
- package/dist/text/piece-table-write.d.ts +4 -0
- package/dist/text/piece-table-write.js +23 -0
- package/dist/write.cjs +175 -0
- package/dist/write.d.cts +5 -0
- package/dist/write.d.ts +5 -0
- package/dist/write.js +174 -0
- 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,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;
|
package/dist/write.d.cts
ADDED
package/dist/write.d.ts
ADDED
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
|
|
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.
|
|
79
|
-
"document-schema.js": "^5.
|
|
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",
|