doc-codec 1.0.2 → 1.1.1

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
@@ -1,4 +1,4 @@
1
- import { Prl } from "./sprm.js";
1
+ import { t as Prl } from "../sprm-Djg5RNiq.js";
2
2
  import { Color } from "document-schema.js";
3
3
  //#region src/prop/chp.d.ts
4
4
  interface CharacterProperties {
@@ -8,9 +8,10 @@ interface CharacterProperties {
8
8
  strike?: boolean;
9
9
  sizePt?: number;
10
10
  color?: Color;
11
+ fontFamily?: string;
11
12
  /** The istd of a character style applied by sprmCIstd, carried so a caller can resolve the style's own name. */
12
13
  istd?: number;
13
14
  }
14
- declare function applyCharacterSprms(prls: readonly Prl[], into: CharacterProperties): CharacterProperties;
15
+ declare function applyCharacterSprms(prls: readonly Prl[], into: CharacterProperties, fonts?: readonly string[]): CharacterProperties;
15
16
  //#endregion
16
17
  export { CharacterProperties, applyCharacterSprms };
package/dist/prop/chp.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DocFormatError } from "../errors.js";
2
- import { readUint16LE, readUint8 } from "../bytes.js";
2
+ import { readInt16LE, readUint16LE, readUint8 } from "../bytes.js";
3
+ import { icoColor, readColorRef } from "../color.js";
3
4
  import { SGC } from "./sprm.js";
4
5
  //#region src/prop/chp.ts
5
6
  /** sprmCFBold: a ToggleOperand switching bold. */
@@ -14,100 +15,18 @@ const SPRM_C_KUL = 10814;
14
15
  const SPRM_C_HPS = 19011;
15
16
  /** sprmCIstd: the istd of a character style to apply. */
16
17
  const SPRM_C_ISTD = 18992;
17
- /** sprmCIco: an Ico value, an index into [MS-DOC] 2.9.126's fixed 17-entry palette. */
18
+ /** sprmCIco: an Ico value, an index into [MS-DOC] 2.9.119's fixed 17-entry palette. */
18
19
  const SPRM_C_ICO = 10818;
19
20
  /** sprmCCv: a COLORREF, the richer colour sprm that supersedes sprmCIco where both appear. */
20
21
  const SPRM_C_CV = 26736;
22
+ /** sprmCRgFtc0: a 2-byte signed index into the font table (SttbfFfn) naming the font used "only if the conditions for using [sprmCRgFtc1/sprmCRgFtc2/sprmCFtcBi] do not apply" -- the default (non-East-Asian, non-complex-script) font, which is the only one this package reads or writes. */
23
+ const SPRM_C_RG_FTC_0 = 19023;
21
24
  /** ToggleOperand, [MS-DOC] 2.9.336. 0x80 and 0x81 are relative to the style's own value rather than absolute. */
22
25
  const TOGGLE_OFF = 0;
23
26
  const TOGGLE_ON = 1;
24
27
  const TOGGLE_INHERIT_FROM_STYLE = 128;
25
28
  const TOGGLE_INVERT_STYLE = 129;
26
29
  const HALF_POINTS_PER_POINT = 2;
27
- const COLOR_COMPONENT_MAX = 255;
28
- const ICO_PALETTE = [
29
- void 0,
30
- [
31
- 0,
32
- 0,
33
- 0
34
- ],
35
- [
36
- 0,
37
- 0,
38
- 255
39
- ],
40
- [
41
- 0,
42
- 255,
43
- 255
44
- ],
45
- [
46
- 0,
47
- 255,
48
- 0
49
- ],
50
- [
51
- 255,
52
- 0,
53
- 255
54
- ],
55
- [
56
- 255,
57
- 0,
58
- 0
59
- ],
60
- [
61
- 255,
62
- 255,
63
- 0
64
- ],
65
- [
66
- 255,
67
- 255,
68
- 255
69
- ],
70
- [
71
- 0,
72
- 0,
73
- 128
74
- ],
75
- [
76
- 0,
77
- 128,
78
- 128
79
- ],
80
- [
81
- 0,
82
- 128,
83
- 0
84
- ],
85
- [
86
- 128,
87
- 0,
88
- 128
89
- ],
90
- [
91
- 128,
92
- 0,
93
- 128
94
- ],
95
- [
96
- 128,
97
- 128,
98
- 0
99
- ],
100
- [
101
- 128,
102
- 128,
103
- 128
104
- ],
105
- [
106
- 192,
107
- 192,
108
- 192
109
- ]
110
- ];
111
30
  function toggle(operand, current) {
112
31
  const value = readUint8(operand, 0);
113
32
  switch (value) {
@@ -118,26 +37,7 @@ function toggle(operand, current) {
118
37
  default: throw new DocFormatError(`ToggleOperand value 0x${value.toString(16)} is none of the four [MS-DOC] 2.9.336 defines (0x00, 0x01, 0x80, 0x81)`);
119
38
  }
120
39
  }
121
- function icoColor(value) {
122
- if (value >= ICO_PALETTE.length) throw new DocFormatError(`Ico value 0x${value.toString(16)} is not less than 0x11, the bound [MS-DOC] 2.9.126 places on the palette`);
123
- const entry = ICO_PALETTE[value];
124
- if (entry === void 0) return void 0;
125
- const [r, g, b] = entry;
126
- return {
127
- r: r / COLOR_COMPONENT_MAX,
128
- g: g / COLOR_COMPONENT_MAX,
129
- b: b / COLOR_COMPONENT_MAX
130
- };
131
- }
132
- function colorRefColor(operand) {
133
- if (readUint8(operand, 3) !== 0) return void 0;
134
- return {
135
- r: readUint8(operand, 0) / COLOR_COMPONENT_MAX,
136
- g: readUint8(operand, 1) / COLOR_COMPONENT_MAX,
137
- b: readUint8(operand, 2) / COLOR_COMPONENT_MAX
138
- };
139
- }
140
- function applyCharacterSprms(prls, into) {
40
+ function applyCharacterSprms(prls, into, fonts) {
141
41
  for (const prl of prls) {
142
42
  if (prl.sprm.sgc !== SGC.character) continue;
143
43
  switch (prl.sprm.value) {
@@ -162,7 +62,15 @@ function applyCharacterSprms(prls, into) {
162
62
  case SPRM_C_ICO:
163
63
  into.color = icoColor(readUint8(prl.operand, 0));
164
64
  break;
165
- case SPRM_C_CV: into.color = colorRefColor(prl.operand);
65
+ case SPRM_C_CV:
66
+ into.color = readColorRef(prl.operand, 0);
67
+ break;
68
+ case SPRM_C_RG_FTC_0: {
69
+ const index = readInt16LE(prl.operand, 0);
70
+ const name = fonts?.[index];
71
+ if (name !== void 0) into.fontFamily = name;
72
+ break;
73
+ }
166
74
  }
167
75
  }
168
76
  return into;
@@ -0,0 +1,139 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
+ require("./fkp.cjs");
4
+ //#region src/prop/fkp-write.ts
5
+ const MAX_CRUN = 101;
6
+ const MAX_CPARA = 29;
7
+ /** A BxPap is a 1-byte bOffset followed by a 12-byte PHE this package never populates (see fkp.ts: "the specification says SHOULD be zero and SHOULD be ignored"). */
8
+ const BX_PAP_SIZE = 13;
9
+ /** A Chpx's own cb is one byte, and a PapxInFkp's one-byte cb/cb' halves the GrpPrlAndIstd length -- both single-byte fields, so 255 bounds a Chpx record's cb and 510 bounds a PapxInFkp's GrpPrlAndIstd (2 x 255) before either can no longer be expressed. */
10
+ const MAX_CHPX_RECORD_GRPPRL = 255;
11
+ const MAX_GRP_PRL_AND_ISTD = 510;
12
+ function buildChpxPage(runs, fcLim) {
13
+ const crun = runs.length;
14
+ if (crun < 1 || crun > MAX_CRUN) return void 0;
15
+ const page = /* @__PURE__ */ new Uint8Array(512);
16
+ const view = new DataView(page.buffer);
17
+ runs.forEach((run, index) => {
18
+ view.setUint32(index * 4, run.fc, true);
19
+ });
20
+ view.setUint32(crun * 4, fcLim, true);
21
+ page[511] = crun;
22
+ const rgbStart = (crun + 1) * 4;
23
+ const frontUsed = rgbStart + crun;
24
+ let writeAt = 511;
25
+ for (let index = 0; index < crun; index += 1) {
26
+ const run = runs[index];
27
+ if (run === void 0) throw new require_errors.DocFormatError("run missing from a batch already sized");
28
+ const rgbAt = rgbStart + index;
29
+ if (run.grpprl === void 0) {
30
+ page[rgbAt] = 0;
31
+ continue;
32
+ }
33
+ if (run.grpprl.length > MAX_CHPX_RECORD_GRPPRL) return void 0;
34
+ const record = [run.grpprl.length, ...run.grpprl];
35
+ writeAt -= record.length;
36
+ writeAt -= writeAt % 2;
37
+ if (writeAt < frontUsed) return void 0;
38
+ page.set(record, writeAt);
39
+ page[rgbAt] = writeAt / 2;
40
+ }
41
+ return page;
42
+ }
43
+ function buildPapxPage(paragraphs, fcLim) {
44
+ const cpara = paragraphs.length;
45
+ if (cpara < 1 || cpara > MAX_CPARA) return void 0;
46
+ const page = /* @__PURE__ */ new Uint8Array(512);
47
+ const view = new DataView(page.buffer);
48
+ paragraphs.forEach((paragraph, index) => {
49
+ view.setUint32(index * 4, paragraph.fc, true);
50
+ });
51
+ view.setUint32(cpara * 4, fcLim, true);
52
+ page[511] = cpara;
53
+ const bxPapStart = (cpara + 1) * 4;
54
+ const frontUsed = bxPapStart + cpara * BX_PAP_SIZE;
55
+ let writeAt = 511;
56
+ for (let index = 0; index < cpara; index += 1) {
57
+ const paragraph = paragraphs[index];
58
+ if (paragraph === void 0) throw new require_errors.DocFormatError("paragraph missing from a batch already sized");
59
+ const bxPapAt = bxPapStart + index * BX_PAP_SIZE;
60
+ const grpPrlAndIstd = [
61
+ paragraph.istd & 255,
62
+ paragraph.istd >> 8 & 255,
63
+ ...paragraph.grpprl
64
+ ];
65
+ if (grpPrlAndIstd.length > MAX_GRP_PRL_AND_ISTD) return void 0;
66
+ const record = grpPrlAndIstd.length % 2 === 1 ? [(grpPrlAndIstd.length + 1) / 2, ...grpPrlAndIstd] : [
67
+ 0,
68
+ grpPrlAndIstd.length / 2,
69
+ ...grpPrlAndIstd
70
+ ];
71
+ writeAt -= record.length;
72
+ writeAt -= writeAt % 2;
73
+ if (writeAt < frontUsed) return void 0;
74
+ page.set(record, writeAt);
75
+ page[bxPapAt] = writeAt / 2;
76
+ }
77
+ return page;
78
+ }
79
+ function splitIntoBatches(items, fits, what) {
80
+ const batches = [];
81
+ let batch = [];
82
+ for (const item of items) {
83
+ const candidate = [...batch, item];
84
+ if (fits(candidate)) {
85
+ batch = candidate;
86
+ continue;
87
+ }
88
+ if (batch.length === 0) throw new require_errors.DocFormatError(`a single ${what} does not fit in one 512-byte formatted disk page`);
89
+ batches.push(batch);
90
+ batch = [item];
91
+ if (!fits(batch)) throw new require_errors.DocFormatError(`a single ${what} does not fit in one 512-byte formatted disk page`);
92
+ }
93
+ if (batch.length > 0) batches.push(batch);
94
+ return batches;
95
+ }
96
+ /** Splits `runs` across as many ChpxFkp pages as needed. `fcLim` is the exclusive end of the whole run sequence (the byte offset one past the document's own text). */
97
+ function buildChpxPages(runs, fcLim) {
98
+ if (runs.length === 0) throw new require_errors.DocFormatError("buildChpxPages requires at least one run");
99
+ const batches = splitIntoBatches(runs, (batch) => buildChpxPage(batch, 0) !== void 0, "character-formatting run");
100
+ return batches.map((batch, index) => {
101
+ const next = batches[index + 1];
102
+ const page = buildChpxPage(batch, next === void 0 ? fcLim : next[0]?.fc ?? fcLim);
103
+ if (page === void 0) throw new require_errors.DocFormatError("a ChpxFkp batch that fit during splitting no longer fits when finalised; this is an internal defect");
104
+ return page;
105
+ });
106
+ }
107
+ /** Splits `paragraphs` across as many PapxFkp pages as needed. `fcLim` is the exclusive end of the whole paragraph sequence. */
108
+ function buildPapxPages(paragraphs, fcLim) {
109
+ if (paragraphs.length === 0) throw new require_errors.DocFormatError("buildPapxPages requires at least one paragraph");
110
+ const batches = splitIntoBatches(paragraphs, (batch) => buildPapxPage(batch, 0) !== void 0, "paragraph-formatting record");
111
+ return batches.map((batch, index) => {
112
+ const next = batches[index + 1];
113
+ const page = buildPapxPage(batch, next === void 0 ? fcLim : next[0]?.fc ?? fcLim);
114
+ if (page === void 0) throw new require_errors.DocFormatError("a PapxFkp batch that fit during splitting no longer fits when finalised; this is an internal defect");
115
+ return page;
116
+ });
117
+ }
118
+ /** A built page's own first rgfc entry -- the byte offset of the first run or paragraph it covers. Reading it back out of the page's own bytes, rather than threading it through as separate metadata, keeps the bin table's keys and the page's own content provably in agreement: there is exactly one place either could disagree with itself. */
119
+ function firstFcOfPage(page) {
120
+ return new DataView(page.buffer, page.byteOffset, 4).getUint32(0, true);
121
+ }
122
+ /** Builds a PlcBteChpx or PlcBtePapx: the bin table mapping each page's own starting byte offset (plus a final terminating fcLim) to its page number. `firstFcs` must carry exactly one more entry than `pageNumbers` -- see plc.ts's own PLC shape. */
123
+ function buildPropertyBinTable(firstFcs, pageNumbers) {
124
+ if (firstFcs.length !== pageNumbers.length + 1) throw new require_errors.DocFormatError(`buildPropertyBinTable was given ${firstFcs.length} keys for ${pageNumbers.length} page numbers; a PLC needs exactly one more key than element`);
125
+ const bytes = new Uint8Array(firstFcs.length * 4 + pageNumbers.length * 4);
126
+ const view = new DataView(bytes.buffer);
127
+ firstFcs.forEach((fc, index) => {
128
+ view.setUint32(index * 4, fc, true);
129
+ });
130
+ pageNumbers.forEach((pageNumber, index) => {
131
+ view.setUint32(firstFcs.length * 4 + index * 4, pageNumber, true);
132
+ });
133
+ return bytes;
134
+ }
135
+ //#endregion
136
+ exports.buildChpxPages = buildChpxPages;
137
+ exports.buildPapxPages = buildPapxPages;
138
+ exports.buildPropertyBinTable = buildPropertyBinTable;
139
+ exports.firstFcOfPage = firstFcOfPage;
@@ -0,0 +1,21 @@
1
+ //#region src/prop/fkp-write.d.ts
2
+ interface ChpxRunToWrite {
3
+ readonly fc: number;
4
+ /** undefined writes rgb 0 -- "no exception, default properties" -- exactly what parseChpxFkp reads back as undefined. */
5
+ readonly grpprl: readonly number[] | undefined;
6
+ }
7
+ interface PapxParagraphToWrite {
8
+ readonly fc: number;
9
+ readonly istd: number;
10
+ readonly grpprl: readonly number[];
11
+ }
12
+ /** Splits `runs` across as many ChpxFkp pages as needed. `fcLim` is the exclusive end of the whole run sequence (the byte offset one past the document's own text). */
13
+ declare function buildChpxPages(runs: readonly ChpxRunToWrite[], fcLim: number): Uint8Array[];
14
+ /** Splits `paragraphs` across as many PapxFkp pages as needed. `fcLim` is the exclusive end of the whole paragraph sequence. */
15
+ declare function buildPapxPages(paragraphs: readonly PapxParagraphToWrite[], fcLim: number): Uint8Array[];
16
+ /** A built page's own first rgfc entry -- the byte offset of the first run or paragraph it covers. Reading it back out of the page's own bytes, rather than threading it through as separate metadata, keeps the bin table's keys and the page's own content provably in agreement: there is exactly one place either could disagree with itself. */
17
+ declare function firstFcOfPage(page: Uint8Array): number;
18
+ /** Builds a PlcBteChpx or PlcBtePapx: the bin table mapping each page's own starting byte offset (plus a final terminating fcLim) to its page number. `firstFcs` must carry exactly one more entry than `pageNumbers` -- see plc.ts's own PLC shape. */
19
+ declare function buildPropertyBinTable(firstFcs: readonly number[], pageNumbers: readonly number[]): Uint8Array;
20
+ //#endregion
21
+ export { ChpxRunToWrite, PapxParagraphToWrite, buildChpxPages, buildPapxPages, buildPropertyBinTable, firstFcOfPage };
@@ -0,0 +1,21 @@
1
+ //#region src/prop/fkp-write.d.ts
2
+ interface ChpxRunToWrite {
3
+ readonly fc: number;
4
+ /** undefined writes rgb 0 -- "no exception, default properties" -- exactly what parseChpxFkp reads back as undefined. */
5
+ readonly grpprl: readonly number[] | undefined;
6
+ }
7
+ interface PapxParagraphToWrite {
8
+ readonly fc: number;
9
+ readonly istd: number;
10
+ readonly grpprl: readonly number[];
11
+ }
12
+ /** Splits `runs` across as many ChpxFkp pages as needed. `fcLim` is the exclusive end of the whole run sequence (the byte offset one past the document's own text). */
13
+ declare function buildChpxPages(runs: readonly ChpxRunToWrite[], fcLim: number): Uint8Array[];
14
+ /** Splits `paragraphs` across as many PapxFkp pages as needed. `fcLim` is the exclusive end of the whole paragraph sequence. */
15
+ declare function buildPapxPages(paragraphs: readonly PapxParagraphToWrite[], fcLim: number): Uint8Array[];
16
+ /** A built page's own first rgfc entry -- the byte offset of the first run or paragraph it covers. Reading it back out of the page's own bytes, rather than threading it through as separate metadata, keeps the bin table's keys and the page's own content provably in agreement: there is exactly one place either could disagree with itself. */
17
+ declare function firstFcOfPage(page: Uint8Array): number;
18
+ /** Builds a PlcBteChpx or PlcBtePapx: the bin table mapping each page's own starting byte offset (plus a final terminating fcLim) to its page number. `firstFcs` must carry exactly one more entry than `pageNumbers` -- see plc.ts's own PLC shape. */
19
+ declare function buildPropertyBinTable(firstFcs: readonly number[], pageNumbers: readonly number[]): Uint8Array;
20
+ //#endregion
21
+ export { ChpxRunToWrite, PapxParagraphToWrite, buildChpxPages, buildPapxPages, buildPropertyBinTable, firstFcOfPage };
@@ -0,0 +1,135 @@
1
+ import { DocFormatError } from "../errors.js";
2
+ import "./fkp.js";
3
+ //#region src/prop/fkp-write.ts
4
+ const MAX_CRUN = 101;
5
+ const MAX_CPARA = 29;
6
+ /** A BxPap is a 1-byte bOffset followed by a 12-byte PHE this package never populates (see fkp.ts: "the specification says SHOULD be zero and SHOULD be ignored"). */
7
+ const BX_PAP_SIZE = 13;
8
+ /** A Chpx's own cb is one byte, and a PapxInFkp's one-byte cb/cb' halves the GrpPrlAndIstd length -- both single-byte fields, so 255 bounds a Chpx record's cb and 510 bounds a PapxInFkp's GrpPrlAndIstd (2 x 255) before either can no longer be expressed. */
9
+ const MAX_CHPX_RECORD_GRPPRL = 255;
10
+ const MAX_GRP_PRL_AND_ISTD = 510;
11
+ function buildChpxPage(runs, fcLim) {
12
+ const crun = runs.length;
13
+ if (crun < 1 || crun > MAX_CRUN) return void 0;
14
+ const page = /* @__PURE__ */ new Uint8Array(512);
15
+ const view = new DataView(page.buffer);
16
+ runs.forEach((run, index) => {
17
+ view.setUint32(index * 4, run.fc, true);
18
+ });
19
+ view.setUint32(crun * 4, fcLim, true);
20
+ page[511] = crun;
21
+ const rgbStart = (crun + 1) * 4;
22
+ const frontUsed = rgbStart + crun;
23
+ let writeAt = 511;
24
+ for (let index = 0; index < crun; index += 1) {
25
+ const run = runs[index];
26
+ if (run === void 0) throw new DocFormatError("run missing from a batch already sized");
27
+ const rgbAt = rgbStart + index;
28
+ if (run.grpprl === void 0) {
29
+ page[rgbAt] = 0;
30
+ continue;
31
+ }
32
+ if (run.grpprl.length > MAX_CHPX_RECORD_GRPPRL) return void 0;
33
+ const record = [run.grpprl.length, ...run.grpprl];
34
+ writeAt -= record.length;
35
+ writeAt -= writeAt % 2;
36
+ if (writeAt < frontUsed) return void 0;
37
+ page.set(record, writeAt);
38
+ page[rgbAt] = writeAt / 2;
39
+ }
40
+ return page;
41
+ }
42
+ function buildPapxPage(paragraphs, fcLim) {
43
+ const cpara = paragraphs.length;
44
+ if (cpara < 1 || cpara > MAX_CPARA) return void 0;
45
+ const page = /* @__PURE__ */ new Uint8Array(512);
46
+ const view = new DataView(page.buffer);
47
+ paragraphs.forEach((paragraph, index) => {
48
+ view.setUint32(index * 4, paragraph.fc, true);
49
+ });
50
+ view.setUint32(cpara * 4, fcLim, true);
51
+ page[511] = cpara;
52
+ const bxPapStart = (cpara + 1) * 4;
53
+ const frontUsed = bxPapStart + cpara * BX_PAP_SIZE;
54
+ let writeAt = 511;
55
+ for (let index = 0; index < cpara; index += 1) {
56
+ const paragraph = paragraphs[index];
57
+ if (paragraph === void 0) throw new DocFormatError("paragraph missing from a batch already sized");
58
+ const bxPapAt = bxPapStart + index * BX_PAP_SIZE;
59
+ const grpPrlAndIstd = [
60
+ paragraph.istd & 255,
61
+ paragraph.istd >> 8 & 255,
62
+ ...paragraph.grpprl
63
+ ];
64
+ if (grpPrlAndIstd.length > MAX_GRP_PRL_AND_ISTD) return void 0;
65
+ const record = grpPrlAndIstd.length % 2 === 1 ? [(grpPrlAndIstd.length + 1) / 2, ...grpPrlAndIstd] : [
66
+ 0,
67
+ grpPrlAndIstd.length / 2,
68
+ ...grpPrlAndIstd
69
+ ];
70
+ writeAt -= record.length;
71
+ writeAt -= writeAt % 2;
72
+ if (writeAt < frontUsed) return void 0;
73
+ page.set(record, writeAt);
74
+ page[bxPapAt] = writeAt / 2;
75
+ }
76
+ return page;
77
+ }
78
+ function splitIntoBatches(items, fits, what) {
79
+ const batches = [];
80
+ let batch = [];
81
+ for (const item of items) {
82
+ const candidate = [...batch, item];
83
+ if (fits(candidate)) {
84
+ batch = candidate;
85
+ continue;
86
+ }
87
+ if (batch.length === 0) throw new DocFormatError(`a single ${what} does not fit in one 512-byte formatted disk page`);
88
+ batches.push(batch);
89
+ batch = [item];
90
+ if (!fits(batch)) throw new DocFormatError(`a single ${what} does not fit in one 512-byte formatted disk page`);
91
+ }
92
+ if (batch.length > 0) batches.push(batch);
93
+ return batches;
94
+ }
95
+ /** Splits `runs` across as many ChpxFkp pages as needed. `fcLim` is the exclusive end of the whole run sequence (the byte offset one past the document's own text). */
96
+ function buildChpxPages(runs, fcLim) {
97
+ if (runs.length === 0) throw new DocFormatError("buildChpxPages requires at least one run");
98
+ const batches = splitIntoBatches(runs, (batch) => buildChpxPage(batch, 0) !== void 0, "character-formatting run");
99
+ return batches.map((batch, index) => {
100
+ const next = batches[index + 1];
101
+ const page = buildChpxPage(batch, next === void 0 ? fcLim : next[0]?.fc ?? fcLim);
102
+ if (page === void 0) throw new DocFormatError("a ChpxFkp batch that fit during splitting no longer fits when finalised; this is an internal defect");
103
+ return page;
104
+ });
105
+ }
106
+ /** Splits `paragraphs` across as many PapxFkp pages as needed. `fcLim` is the exclusive end of the whole paragraph sequence. */
107
+ function buildPapxPages(paragraphs, fcLim) {
108
+ if (paragraphs.length === 0) throw new DocFormatError("buildPapxPages requires at least one paragraph");
109
+ const batches = splitIntoBatches(paragraphs, (batch) => buildPapxPage(batch, 0) !== void 0, "paragraph-formatting record");
110
+ return batches.map((batch, index) => {
111
+ const next = batches[index + 1];
112
+ const page = buildPapxPage(batch, next === void 0 ? fcLim : next[0]?.fc ?? fcLim);
113
+ if (page === void 0) throw new DocFormatError("a PapxFkp batch that fit during splitting no longer fits when finalised; this is an internal defect");
114
+ return page;
115
+ });
116
+ }
117
+ /** A built page's own first rgfc entry -- the byte offset of the first run or paragraph it covers. Reading it back out of the page's own bytes, rather than threading it through as separate metadata, keeps the bin table's keys and the page's own content provably in agreement: there is exactly one place either could disagree with itself. */
118
+ function firstFcOfPage(page) {
119
+ return new DataView(page.buffer, page.byteOffset, 4).getUint32(0, true);
120
+ }
121
+ /** Builds a PlcBteChpx or PlcBtePapx: the bin table mapping each page's own starting byte offset (plus a final terminating fcLim) to its page number. `firstFcs` must carry exactly one more entry than `pageNumbers` -- see plc.ts's own PLC shape. */
122
+ function buildPropertyBinTable(firstFcs, pageNumbers) {
123
+ if (firstFcs.length !== pageNumbers.length + 1) throw new DocFormatError(`buildPropertyBinTable was given ${firstFcs.length} keys for ${pageNumbers.length} page numbers; a PLC needs exactly one more key than element`);
124
+ const bytes = new Uint8Array(firstFcs.length * 4 + pageNumbers.length * 4);
125
+ const view = new DataView(bytes.buffer);
126
+ firstFcs.forEach((fc, index) => {
127
+ view.setUint32(index * 4, fc, true);
128
+ });
129
+ pageNumbers.forEach((pageNumber, index) => {
130
+ view.setUint32(firstFcs.length * 4 + index * 4, pageNumber, true);
131
+ });
132
+ return bytes;
133
+ }
134
+ //#endregion
135
+ export { buildChpxPages, buildPapxPages, buildPropertyBinTable, firstFcOfPage };
@@ -0,0 +1,67 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
+ //#region src/prop/pap-write.ts
4
+ /** sprmPJc: a 1-byte logical justification. */
5
+ const SPRM_P_JC = 9313;
6
+ /** sprmPDxaLeft / sprmPDxaLeft1: 2-byte signed twips. There is no writer-side counterpart for sprmPDxaRight (indent from the right margin): pap.ts's own reader folds it into a ParagraphProperties.indentRightPt field, but ContentParagraphSchema (document-schema.js) carries no such field for any writer to round-trip -- the right-indent value the reader computes is simply not part of the shared schema's paragraph vocabulary. */
7
+ const SPRM_P_DXA_LEFT = 33886;
8
+ const SPRM_P_DXA_LEFT1 = 33888;
9
+ /** sprmPDyaBefore / sprmPDyaAfter: 2-byte unsigned twips. */
10
+ const SPRM_P_DYA_BEFORE = 42003;
11
+ const SPRM_P_DYA_AFTER = 42004;
12
+ /** sprmPDyaLine: a 4-byte LSPD (dyaLine int16, fMultLinespace uint16). */
13
+ const SPRM_P_DYA_LINE = 25618;
14
+ /** sprmPFPageBreakBefore: a 1-byte Bool8. */
15
+ const SPRM_P_F_PAGE_BREAK_BEFORE = 9223;
16
+ const TWIPS_PER_POINT = 20;
17
+ const LSPD_MULTIPLE_DIVISOR = 240;
18
+ const LSPD_MAX_MULTIPLE_DYA_LINE = 31680;
19
+ /** sprmPDxaLeft/Right/Left1's signed 2-byte operand range. */
20
+ const MIN_INT16 = -32768;
21
+ const MAX_INT16 = 32767;
22
+ /** sprmPDyaBefore/After's unsigned 2-byte operand range. */
23
+ const MAX_UINT16 = 65535;
24
+ const JC_VALUE = {
25
+ left: 0,
26
+ center: 1,
27
+ right: 2,
28
+ justify: 3
29
+ };
30
+ function pushSprm(bytes, opcode, operand) {
31
+ bytes.push(opcode & 255, opcode >> 8 & 255, ...operand);
32
+ }
33
+ function int16(value, what) {
34
+ const rounded = Math.round(value);
35
+ if (rounded < MIN_INT16 || rounded > MAX_INT16) throw new require_errors.DocFormatError(`${what} is ${rounded} twips, outside the ${MIN_INT16}..${MAX_INT16} range a signed 2-byte sprm operand can hold`);
36
+ const unsigned = rounded < 0 ? rounded + 65536 : rounded;
37
+ return [unsigned & 255, unsigned >> 8 & 255];
38
+ }
39
+ function uint16(value, what) {
40
+ const rounded = Math.round(value);
41
+ if (rounded < 0 || rounded > MAX_UINT16) throw new require_errors.DocFormatError(`${what} is ${rounded} twips, outside the 0..${MAX_UINT16} range an unsigned 2-byte sprm operand can hold`);
42
+ return [rounded & 255, rounded >> 8 & 255];
43
+ }
44
+ function pointsToTwips(pt) {
45
+ return pt * TWIPS_PER_POINT;
46
+ }
47
+ function encodeParagraphGrpprl(paragraph) {
48
+ const bytes = [];
49
+ if (paragraph.alignment !== void 0) pushSprm(bytes, SPRM_P_JC, [JC_VALUE[paragraph.alignment]]);
50
+ if (paragraph.indentLeftPt !== void 0) pushSprm(bytes, SPRM_P_DXA_LEFT, int16(pointsToTwips(paragraph.indentLeftPt), "paragraph indentLeftPt"));
51
+ if (paragraph.indentFirstLinePt !== void 0) pushSprm(bytes, SPRM_P_DXA_LEFT1, int16(pointsToTwips(paragraph.indentFirstLinePt), "paragraph indentFirstLinePt"));
52
+ if (paragraph.spacingBeforePt !== void 0) pushSprm(bytes, SPRM_P_DYA_BEFORE, uint16(pointsToTwips(paragraph.spacingBeforePt), "paragraph spacingBeforePt"));
53
+ if (paragraph.spacingAfterPt !== void 0) pushSprm(bytes, SPRM_P_DYA_AFTER, uint16(pointsToTwips(paragraph.spacingAfterPt), "paragraph spacingAfterPt"));
54
+ if (paragraph.lineSpacing !== void 0) {
55
+ const dyaLine = Math.round(paragraph.lineSpacing * LSPD_MULTIPLE_DIVISOR);
56
+ if (dyaLine < 0 || dyaLine > LSPD_MAX_MULTIPLE_DYA_LINE) throw new require_errors.DocFormatError(`paragraph lineSpacing ${paragraph.lineSpacing} produces an LSPD.dyaLine of ${dyaLine}, outside the 0..${LSPD_MAX_MULTIPLE_DYA_LINE} range the multiplier form permits`);
57
+ pushSprm(bytes, SPRM_P_DYA_LINE, [
58
+ ...int16(dyaLine, "paragraph lineSpacing"),
59
+ 1,
60
+ 0
61
+ ]);
62
+ }
63
+ if (paragraph.pageBreakBefore === true) pushSprm(bytes, SPRM_P_F_PAGE_BREAK_BEFORE, [1]);
64
+ return bytes;
65
+ }
66
+ //#endregion
67
+ exports.encodeParagraphGrpprl = encodeParagraphGrpprl;
@@ -0,0 +1,5 @@
1
+ import { ContentParagraph } from "document-schema.js";
2
+ //#region src/prop/pap-write.d.ts
3
+ declare function encodeParagraphGrpprl(paragraph: Pick<ContentParagraph, "alignment" | "indentLeftPt" | "indentFirstLinePt" | "spacingBeforePt" | "spacingAfterPt" | "lineSpacing" | "pageBreakBefore">): number[];
4
+ //#endregion
5
+ export { encodeParagraphGrpprl };
@@ -0,0 +1,5 @@
1
+ import { ContentParagraph } from "document-schema.js";
2
+ //#region src/prop/pap-write.d.ts
3
+ declare function encodeParagraphGrpprl(paragraph: Pick<ContentParagraph, "alignment" | "indentLeftPt" | "indentFirstLinePt" | "spacingBeforePt" | "spacingAfterPt" | "lineSpacing" | "pageBreakBefore">): number[];
4
+ //#endregion
5
+ export { encodeParagraphGrpprl };
@@ -0,0 +1,66 @@
1
+ import { DocFormatError } from "../errors.js";
2
+ //#region src/prop/pap-write.ts
3
+ /** sprmPJc: a 1-byte logical justification. */
4
+ const SPRM_P_JC = 9313;
5
+ /** sprmPDxaLeft / sprmPDxaLeft1: 2-byte signed twips. There is no writer-side counterpart for sprmPDxaRight (indent from the right margin): pap.ts's own reader folds it into a ParagraphProperties.indentRightPt field, but ContentParagraphSchema (document-schema.js) carries no such field for any writer to round-trip -- the right-indent value the reader computes is simply not part of the shared schema's paragraph vocabulary. */
6
+ const SPRM_P_DXA_LEFT = 33886;
7
+ const SPRM_P_DXA_LEFT1 = 33888;
8
+ /** sprmPDyaBefore / sprmPDyaAfter: 2-byte unsigned twips. */
9
+ const SPRM_P_DYA_BEFORE = 42003;
10
+ const SPRM_P_DYA_AFTER = 42004;
11
+ /** sprmPDyaLine: a 4-byte LSPD (dyaLine int16, fMultLinespace uint16). */
12
+ const SPRM_P_DYA_LINE = 25618;
13
+ /** sprmPFPageBreakBefore: a 1-byte Bool8. */
14
+ const SPRM_P_F_PAGE_BREAK_BEFORE = 9223;
15
+ const TWIPS_PER_POINT = 20;
16
+ const LSPD_MULTIPLE_DIVISOR = 240;
17
+ const LSPD_MAX_MULTIPLE_DYA_LINE = 31680;
18
+ /** sprmPDxaLeft/Right/Left1's signed 2-byte operand range. */
19
+ const MIN_INT16 = -32768;
20
+ const MAX_INT16 = 32767;
21
+ /** sprmPDyaBefore/After's unsigned 2-byte operand range. */
22
+ const MAX_UINT16 = 65535;
23
+ const JC_VALUE = {
24
+ left: 0,
25
+ center: 1,
26
+ right: 2,
27
+ justify: 3
28
+ };
29
+ function pushSprm(bytes, opcode, operand) {
30
+ bytes.push(opcode & 255, opcode >> 8 & 255, ...operand);
31
+ }
32
+ function int16(value, what) {
33
+ const rounded = Math.round(value);
34
+ if (rounded < MIN_INT16 || rounded > MAX_INT16) throw new DocFormatError(`${what} is ${rounded} twips, outside the ${MIN_INT16}..${MAX_INT16} range a signed 2-byte sprm operand can hold`);
35
+ const unsigned = rounded < 0 ? rounded + 65536 : rounded;
36
+ return [unsigned & 255, unsigned >> 8 & 255];
37
+ }
38
+ function uint16(value, what) {
39
+ const rounded = Math.round(value);
40
+ if (rounded < 0 || rounded > MAX_UINT16) throw new DocFormatError(`${what} is ${rounded} twips, outside the 0..${MAX_UINT16} range an unsigned 2-byte sprm operand can hold`);
41
+ return [rounded & 255, rounded >> 8 & 255];
42
+ }
43
+ function pointsToTwips(pt) {
44
+ return pt * TWIPS_PER_POINT;
45
+ }
46
+ function encodeParagraphGrpprl(paragraph) {
47
+ const bytes = [];
48
+ if (paragraph.alignment !== void 0) pushSprm(bytes, SPRM_P_JC, [JC_VALUE[paragraph.alignment]]);
49
+ if (paragraph.indentLeftPt !== void 0) pushSprm(bytes, SPRM_P_DXA_LEFT, int16(pointsToTwips(paragraph.indentLeftPt), "paragraph indentLeftPt"));
50
+ if (paragraph.indentFirstLinePt !== void 0) pushSprm(bytes, SPRM_P_DXA_LEFT1, int16(pointsToTwips(paragraph.indentFirstLinePt), "paragraph indentFirstLinePt"));
51
+ if (paragraph.spacingBeforePt !== void 0) pushSprm(bytes, SPRM_P_DYA_BEFORE, uint16(pointsToTwips(paragraph.spacingBeforePt), "paragraph spacingBeforePt"));
52
+ if (paragraph.spacingAfterPt !== void 0) pushSprm(bytes, SPRM_P_DYA_AFTER, uint16(pointsToTwips(paragraph.spacingAfterPt), "paragraph spacingAfterPt"));
53
+ if (paragraph.lineSpacing !== void 0) {
54
+ const dyaLine = Math.round(paragraph.lineSpacing * LSPD_MULTIPLE_DIVISOR);
55
+ if (dyaLine < 0 || dyaLine > LSPD_MAX_MULTIPLE_DYA_LINE) throw new DocFormatError(`paragraph lineSpacing ${paragraph.lineSpacing} produces an LSPD.dyaLine of ${dyaLine}, outside the 0..${LSPD_MAX_MULTIPLE_DYA_LINE} range the multiplier form permits`);
56
+ pushSprm(bytes, SPRM_P_DYA_LINE, [
57
+ ...int16(dyaLine, "paragraph lineSpacing"),
58
+ 1,
59
+ 0
60
+ ]);
61
+ }
62
+ if (paragraph.pageBreakBefore === true) pushSprm(bytes, SPRM_P_F_PAGE_BREAK_BEFORE, [1]);
63
+ return bytes;
64
+ }
65
+ //#endregion
66
+ export { encodeParagraphGrpprl };
package/dist/prop/pap.cjs CHANGED
@@ -34,6 +34,11 @@ const SPRM_P_F_TTP = 9239;
34
34
  const SPRM_P_ILVL = 9738;
35
35
  /** sprmPIlfo: which list the paragraph is in, as an index into PlfLfo.rgLfo. */
36
36
  const SPRM_P_ILFO = 17931;
37
+ /** sprmPItap: the paragraph's own table depth -- read only far enough to detect a depth greater than 1 (a table nested inside a table cell), which this package refuses rather than mis-reads. */
38
+ const SPRM_P_ITAP = 26185;
39
+ /** sprmPFInnerTableCell / sprmPFInnerTtp: a nested table's own cell-ending or row-ending mark. Neither is acted on beyond refusing the nested table it signals. */
40
+ const SPRM_P_F_INNER_TABLE_CELL = 9291;
41
+ const SPRM_P_F_INNER_TTP = 9292;
37
42
  const TWIPS_PER_POINT = 20;
38
43
  /** LSPD: "The spacing multiplier is dyaLine/240." */
39
44
  const LSPD_MULTIPLE_DIVISOR = 240;
@@ -126,6 +131,11 @@ function applyParagraphSprms(prls, into) {
126
131
  into.listId = ilfo === ILFO_NOT_IN_LIST || ilfo === ILFO_NOT_IN_LIST_ALT ? void 0 : Math.abs(ilfo);
127
132
  break;
128
133
  }
134
+ case SPRM_P_ITAP:
135
+ into.tableDepth = require_bytes.readUint32LE(prl.operand, 0);
136
+ break;
137
+ case SPRM_P_F_INNER_TABLE_CELL:
138
+ case SPRM_P_F_INNER_TTP: if (require_bytes.readUint8(prl.operand, 0) !== 0) into.nestedTableMark = true;
129
139
  }
130
140
  }
131
141
  return into;