@shbernal/ts-xlsx 3.1.0 → 3.2.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 (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -1,3 +1,43 @@
1
+ /** The header's first eight bytes, as the two little-endian `u32`s both directions handle them as. */
2
+ export declare const CFB_SIGNATURE: {
3
+ readonly low: 3759263696;
4
+ readonly high: 3776623009;
5
+ };
6
+ /** Byte offsets of the header fields either direction reads or writes ([MS-CFB] 2.2). */
7
+ export declare const HEADER_FIELD: {
8
+ readonly signatureLow: 0;
9
+ readonly signatureHigh: 4;
10
+ readonly minorVersion: 24;
11
+ readonly majorVersion: 26;
12
+ readonly byteOrder: 28;
13
+ readonly sectorShift: 30;
14
+ readonly miniSectorShift: 32;
15
+ readonly fatSectors: 44;
16
+ readonly firstDirectorySector: 48;
17
+ readonly miniStreamCutoff: 56;
18
+ readonly firstMiniFatSector: 60;
19
+ readonly miniFatSectors: 64;
20
+ readonly firstDifatSector: 68;
21
+ readonly difatSectors: 72;
22
+ readonly difat: 76;
23
+ };
24
+ /** Byte offsets within a directory entry, from the entry's start ([MS-CFB] 2.6.1). The name's UTF-16
25
+ * code units begin at offset 0. */
26
+ export declare const DIR_ENTRY_FIELD: {
27
+ readonly nameLength: 64;
28
+ readonly objectType: 66;
29
+ readonly color: 67;
30
+ readonly leftSibling: 68;
31
+ readonly rightSibling: 72;
32
+ readonly child: 76;
33
+ readonly startSector: 116;
34
+ readonly sizeLow: 120;
35
+ readonly sizeHigh: 124;
36
+ };
37
+ /** FAT-sector pointers the header holds before a DIFAT sector is needed ([MS-CFB] 2.2). */
38
+ export declare const HEADER_DIFAT_SLOTS = 109;
39
+ /** 64-byte mini sectors: [MS-CFB] 2.2 fixes the shift at 6 for both versions. */
40
+ export declare const MINI_SECTOR_SHIFT = 6;
1
41
  /** Sector chain markers ([MS-CFB] 2.2). */
2
42
  export declare const FREESECT = 4294967295;
3
43
  export declare const ENDOFCHAIN = 4294967294;
@@ -30,3 +70,10 @@ export declare const DIR_ENTRY_SIZE = 128;
30
70
  /** 32 UTF-16 code units including the NUL terminator. This is also why a VBA module name is capped
31
71
  * at 31: the module is a stream, and the stream's name is the module's. */
32
72
  export declare const MAX_NAME_CHARS = 31;
73
+ /**
74
+ * Whether two directory-entry names are the same entry. [MS-CFB] 2.6.4 orders siblings by length and
75
+ * then by their uppercase forms, and forbids two siblings that compare equal, so a host looking a name
76
+ * up treats `dir` and `DIR` as one entry. A lookup here does the same, or a file whose producer cased a
77
+ * name differently would read as missing a stream it has.
78
+ */
79
+ export declare function sameEntryName(a: string, b: string): boolean;
@@ -1,3 +1,34 @@
1
+ export const CFB_SIGNATURE = { low: 0xe011cfd0, high: 0xe11ab1a1 };
2
+ export const HEADER_FIELD = {
3
+ signatureLow: 0,
4
+ signatureHigh: 4,
5
+ minorVersion: 24,
6
+ majorVersion: 26,
7
+ byteOrder: 28,
8
+ sectorShift: 30,
9
+ miniSectorShift: 32,
10
+ fatSectors: 44,
11
+ firstDirectorySector: 48,
12
+ miniStreamCutoff: 56,
13
+ firstMiniFatSector: 60,
14
+ miniFatSectors: 64,
15
+ firstDifatSector: 68,
16
+ difatSectors: 72,
17
+ difat: 76,
18
+ };
19
+ export const DIR_ENTRY_FIELD = {
20
+ nameLength: 64,
21
+ objectType: 66,
22
+ color: 67,
23
+ leftSibling: 68,
24
+ rightSibling: 72,
25
+ child: 76,
26
+ startSector: 116,
27
+ sizeLow: 120,
28
+ sizeHigh: 124,
29
+ };
30
+ export const HEADER_DIFAT_SLOTS = 109;
31
+ export const MINI_SECTOR_SHIFT = 6;
1
32
  export const FREESECT = 0xffffffff;
2
33
  export const ENDOFCHAIN = 0xfffffffe;
3
34
  export const FATSECT = 0xfffffffd;
@@ -11,3 +42,6 @@ export const TYPE_ROOT = 5;
11
42
  export const MINI_STREAM_CUTOFF = 4096;
12
43
  export const DIR_ENTRY_SIZE = 128;
13
44
  export const MAX_NAME_CHARS = 31;
45
+ export function sameEntryName(a, b) {
46
+ return a.length === b.length && a.toUpperCase() === b.toUpperCase();
47
+ }
@@ -13,10 +13,9 @@ export type CfbNode = CfbStream | CfbStorage;
13
13
  *
14
14
  * Exported beside the union rather than kept private, because the discriminant is `'data' in node`
15
15
  * and that is the sort of test callers re-spell inline: `project-editor.ts` had it three times, once
16
- * negated. The name says what the test means, and the pair says that a node is one or the other.
16
+ * negated. The name says what the test means, and a node it answers `false` for is a storage.
17
17
  */
18
18
  export declare function isStream(node: CfbNode): node is CfbStream;
19
- export declare function isStorage(node: CfbNode): node is CfbStorage;
20
19
  /**
21
20
  * Encode a hierarchy of storages and streams into a v3 compound file. The Root Entry is synthesized
22
21
  * automatically; `root` is its top-level children. Every stream becomes a directory entry reachable both
@@ -1,17 +1,14 @@
1
1
  import { quoted } from '../errors.js';
2
- import { DIFSECT, DIR_ENTRY_SIZE, ENDOFCHAIN, FATSECT, FREESECT, MAX_NAME_CHARS, MINI_STREAM_CUTOFF, NOSTREAM, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
2
+ import { CFB_SIGNATURE, DIFSECT, DIR_ENTRY_FIELD, DIR_ENTRY_SIZE, ENDOFCHAIN, FATSECT, FREESECT, HEADER_DIFAT_SLOTS, HEADER_FIELD, MAX_NAME_CHARS, MINI_SECTOR_SHIFT, MINI_STREAM_CUTOFF, NOSTREAM, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
3
3
  import { VbaAuthorError } from './errors.js';
4
4
  export function isStream(node) {
5
5
  return 'data' in node;
6
6
  }
7
- export function isStorage(node) {
8
- return !isStream(node);
9
- }
10
- const SECTOR = 512;
11
- const MINI_SECTOR = 64;
7
+ const SECTOR_SHIFT = 9;
8
+ const SECTOR = 1 << SECTOR_SHIFT;
9
+ const MINI_SECTOR = 1 << MINI_SECTOR_SHIFT;
12
10
  const ENTRIES_PER_DIR_SECTOR = SECTOR / DIR_ENTRY_SIZE;
13
11
  const FAT_ENTRIES_PER_SECTOR = SECTOR / 4;
14
- const DIFAT_HEADER_SLOTS = 109;
15
12
  const COLOR_BLACK = 1;
16
13
  export function writeCompoundFile(root) {
17
14
  const entries = [];
@@ -82,16 +79,16 @@ export function writeCompoundFile(root) {
82
79
  for (;;) {
83
80
  const total = baseSectors + fatSectors + difatSectors;
84
81
  const needFat = Math.ceil(total / FAT_ENTRIES_PER_SECTOR);
85
- const needDifat = needFat > DIFAT_HEADER_SLOTS
86
- ? Math.ceil((needFat - DIFAT_HEADER_SLOTS) / (FAT_ENTRIES_PER_SECTOR - 1))
82
+ const needDifat = needFat > HEADER_DIFAT_SLOTS
83
+ ? Math.ceil((needFat - HEADER_DIFAT_SLOTS) / (FAT_ENTRIES_PER_SECTOR - 1))
87
84
  : 0;
88
85
  if (needFat === fatSectors && needDifat === difatSectors)
89
86
  break;
90
87
  fatSectors = needFat;
91
88
  difatSectors = needDifat;
92
89
  }
93
- if (fatSectors > DIFAT_HEADER_SLOTS) {
94
- throw new VbaAuthorError(`project needs ${fatSectors} FAT sectors, exceeding the ${DIFAT_HEADER_SLOTS}-sector single-header bound`);
90
+ if (fatSectors > HEADER_DIFAT_SLOTS) {
91
+ throw new VbaAuthorError(`project needs ${fatSectors} FAT sectors, exceeding the ${HEADER_DIFAT_SLOTS}-sector single-header bound`);
95
92
  }
96
93
  let cursor = 0;
97
94
  const dirStart = cursor;
@@ -158,7 +155,7 @@ export function writeCompoundFile(root) {
158
155
  for (let d = 0; d < difatSectors; d++) {
159
156
  const base = at(difatStart + d);
160
157
  for (let i = 0; i < FAT_ENTRIES_PER_SECTOR - 1; i++) {
161
- const fatIdx = DIFAT_HEADER_SLOTS + d * (FAT_ENTRIES_PER_SECTOR - 1) + i;
158
+ const fatIdx = HEADER_DIFAT_SLOTS + d * (FAT_ENTRIES_PER_SECTOR - 1) + i;
162
159
  dv.setUint32(base + i * 4, fatIdx < fatSectors ? fatStart + fatIdx : FREESECT, true);
163
160
  }
164
161
  dv.setUint32(base + (FAT_ENTRIES_PER_SECTOR - 1) * 4, d < difatSectors - 1 ? difatStart + d + 1 : ENDOFCHAIN, true);
@@ -201,33 +198,33 @@ function compareNames(a, b) {
201
198
  return ua < ub ? -1 : ua > ub ? 1 : 0;
202
199
  }
203
200
  function writeHeader(dv, p) {
204
- dv.setUint32(0, 0xe011cfd0, true);
205
- dv.setUint32(4, 0xe11ab1a1, true);
206
- dv.setUint16(24, 0x003e, true);
207
- dv.setUint16(26, 0x0003, true);
208
- dv.setUint16(28, 0xfffe, true);
209
- dv.setUint16(30, 9, true);
210
- dv.setUint16(32, 6, true);
211
- dv.setUint32(44, p.fatSectors, true);
212
- dv.setUint32(48, p.dirStart, true);
213
- dv.setUint32(56, MINI_STREAM_CUTOFF, true);
214
- dv.setUint32(60, p.miniFatStart, true);
215
- dv.setUint32(64, p.miniFatSectors, true);
216
- dv.setUint32(68, p.difatSectors > 0 ? p.difatStart : ENDOFCHAIN, true);
217
- dv.setUint32(72, p.difatSectors, true);
218
- for (let i = 0; i < DIFAT_HEADER_SLOTS; i++) {
219
- dv.setUint32(76 + i * 4, i < p.fatSectors ? p.fatStart + i : FREESECT, true);
201
+ dv.setUint32(HEADER_FIELD.signatureLow, CFB_SIGNATURE.low, true);
202
+ dv.setUint32(HEADER_FIELD.signatureHigh, CFB_SIGNATURE.high, true);
203
+ dv.setUint16(HEADER_FIELD.minorVersion, 0x003e, true);
204
+ dv.setUint16(HEADER_FIELD.majorVersion, 0x0003, true);
205
+ dv.setUint16(HEADER_FIELD.byteOrder, 0xfffe, true);
206
+ dv.setUint16(HEADER_FIELD.sectorShift, SECTOR_SHIFT, true);
207
+ dv.setUint16(HEADER_FIELD.miniSectorShift, MINI_SECTOR_SHIFT, true);
208
+ dv.setUint32(HEADER_FIELD.fatSectors, p.fatSectors, true);
209
+ dv.setUint32(HEADER_FIELD.firstDirectorySector, p.dirStart, true);
210
+ dv.setUint32(HEADER_FIELD.miniStreamCutoff, MINI_STREAM_CUTOFF, true);
211
+ dv.setUint32(HEADER_FIELD.firstMiniFatSector, p.miniFatStart, true);
212
+ dv.setUint32(HEADER_FIELD.miniFatSectors, p.miniFatSectors, true);
213
+ dv.setUint32(HEADER_FIELD.firstDifatSector, p.difatSectors > 0 ? p.difatStart : ENDOFCHAIN, true);
214
+ dv.setUint32(HEADER_FIELD.difatSectors, p.difatSectors, true);
215
+ for (let i = 0; i < HEADER_DIFAT_SLOTS; i++) {
216
+ dv.setUint32(HEADER_FIELD.difat + i * 4, i < p.fatSectors ? p.fatStart + i : FREESECT, true);
220
217
  }
221
218
  }
222
219
  function writeDirEntry(dv, off, e) {
223
220
  for (let i = 0; i < e.name.length; i++)
224
221
  dv.setUint16(off + i * 2, e.name.charCodeAt(i), true);
225
- dv.setUint16(off + 64, (e.name.length + 1) * 2, true);
226
- dv.setUint8(off + 66, e.type);
227
- dv.setUint8(off + 67, COLOR_BLACK);
228
- dv.setUint32(off + 68, e.left, true);
229
- dv.setUint32(off + 72, e.right, true);
230
- dv.setUint32(off + 76, e.child, true);
231
- dv.setUint32(off + 116, e.startSector, true);
232
- dv.setUint32(off + 120, e.size, true);
222
+ dv.setUint16(off + DIR_ENTRY_FIELD.nameLength, (e.name.length + 1) * 2, true);
223
+ dv.setUint8(off + DIR_ENTRY_FIELD.objectType, e.type);
224
+ dv.setUint8(off + DIR_ENTRY_FIELD.color, COLOR_BLACK);
225
+ dv.setUint32(off + DIR_ENTRY_FIELD.leftSibling, e.left, true);
226
+ dv.setUint32(off + DIR_ENTRY_FIELD.rightSibling, e.right, true);
227
+ dv.setUint32(off + DIR_ENTRY_FIELD.child, e.child, true);
228
+ dv.setUint32(off + DIR_ENTRY_FIELD.startSector, e.startSector, true);
229
+ dv.setUint32(off + DIR_ENTRY_FIELD.sizeLow, e.size, true);
233
230
  }
package/dist/vba/cfb.d.ts CHANGED
@@ -2,10 +2,15 @@ import type { CfbNode } from './cfb-writer.ts';
2
2
  export declare class CompoundFile {
3
3
  #private;
4
4
  constructor(buf: Uint8Array);
5
- /** List every stream/storage name in the directory (order as stored). */
6
- names(): string[];
7
5
  /**
8
- * Read a stream's raw bytes by exact entry name, or `undefined` if absent.
6
+ * Read a stream's raw bytes by its storage path from the root (`['VBA', 'dir']`), or `undefined` when
7
+ * no stream sits there. Each segment is matched among one storage's children, as [MS-CFB] compares
8
+ * names, by walking the sibling tree that storage links.
9
+ *
10
+ * A path because a name is not an address. [MS-OVBA] puts `PROJECT` at the root, `dir` and every
11
+ * module stream in `VBA`, and each UserForm's `f` and `o` in a storage of its own, so a bare name
12
+ * repeats. Looked up by name, the first entry in directory order won: a module named `f` read a
13
+ * UserForm's form data, and an edit wrote a module named `PROJECT` over the root `PROJECT`.
9
14
  *
10
15
  * @returns bytes that may be a **view onto the caller's buffer** rather than a copy. A stream small
11
16
  * enough to fit in one sector is assembled by `concat` from a single chunk, and `concat` hands a
@@ -15,7 +20,7 @@ export declare class CompoundFile {
15
20
  * thing that is true until someone writes the first mutation, and then true only for large
16
21
  * streams. Copy before mutating.
17
22
  */
18
- readStream(name: string): Uint8Array | undefined;
23
+ readStream(path: readonly string[]): Uint8Array | undefined;
19
24
  /**
20
25
  * Reconstruct the container's top-level children as the writer's node shape, recursing into every
21
26
  * storage, so a caller can swap one stream and re-emit the whole hierarchy with {@link writeCompoundFile}.
package/dist/vba/cfb.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import { quoted } from '../errors.js';
2
2
  import { concat, decodeUtf16le, readU16, readU32 } from './bytes.js';
3
- import { DIR_ENTRY_SIZE, MAX_REGULAR_SECTOR, MINI_STREAM_CUTOFF, NOSTREAM, TYPE_EMPTY, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
3
+ import { CFB_SIGNATURE, DIR_ENTRY_FIELD, DIR_ENTRY_SIZE, HEADER_DIFAT_SLOTS, HEADER_FIELD, MAX_REGULAR_SECTOR, MINI_SECTOR_SHIFT, MINI_STREAM_CUTOFF, NOSTREAM, sameEntryName, TYPE_EMPTY, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
4
4
  import { VbaParseError } from './errors.js';
5
- const CFB_SIGNATURE_LO = 0xe011cfd0;
6
- const CFB_SIGNATURE_HI = 0xe11ab1a1;
7
5
  const MAX_TREE_DEPTH = 256;
8
6
  export class CompoundFile {
9
7
  #buf;
@@ -19,28 +17,29 @@ export class CompoundFile {
19
17
  this.#buf = buf;
20
18
  if (buf.length < 512)
21
19
  throw new VbaParseError('compound file shorter than its 512-byte header');
22
- if (readU32(this.#buf, 0) !== CFB_SIGNATURE_LO || readU32(this.#buf, 4) !== CFB_SIGNATURE_HI) {
20
+ if (readU32(this.#buf, HEADER_FIELD.signatureLow) !== CFB_SIGNATURE.low ||
21
+ readU32(this.#buf, HEADER_FIELD.signatureHigh) !== CFB_SIGNATURE.high) {
23
22
  throw new VbaParseError('not a compound file (bad OLE2 signature)');
24
23
  }
25
- const sectorShift = readU16(this.#buf, 30);
26
- const miniSectorShift = readU16(this.#buf, 32);
24
+ const sectorShift = readU16(this.#buf, HEADER_FIELD.sectorShift);
25
+ const miniSectorShift = readU16(this.#buf, HEADER_FIELD.miniSectorShift);
27
26
  if (sectorShift !== 9 && sectorShift !== 12) {
28
27
  throw new VbaParseError(`unsupported sector shift ${sectorShift}`);
29
28
  }
30
- if (miniSectorShift !== 6)
29
+ if (miniSectorShift !== MINI_SECTOR_SHIFT)
31
30
  throw new VbaParseError(`unsupported mini-sector shift ${miniSectorShift}`);
32
31
  this.#sectorSize = 1 << sectorShift;
33
32
  this.#miniSectorSize = 1 << miniSectorShift;
34
33
  this.#maxSector = Math.floor(buf.length / this.#sectorSize);
35
- const numFatSectors = readU32(this.#buf, 44);
36
- const firstDirSector = readU32(this.#buf, 48);
37
- this.#miniCutoff = readU32(this.#buf, 56);
34
+ const numFatSectors = readU32(this.#buf, HEADER_FIELD.fatSectors);
35
+ const firstDirSector = readU32(this.#buf, HEADER_FIELD.firstDirectorySector);
36
+ this.#miniCutoff = readU32(this.#buf, HEADER_FIELD.miniStreamCutoff);
38
37
  if (this.#miniCutoff !== MINI_STREAM_CUTOFF) {
39
38
  throw new VbaParseError(`unsupported mini-stream cutoff ${this.#miniCutoff}`);
40
39
  }
41
- const firstMiniFatSector = readU32(this.#buf, 60);
42
- const firstDifatSector = readU32(this.#buf, 68);
43
- const numDifatSectors = readU32(this.#buf, 72);
40
+ const firstMiniFatSector = readU32(this.#buf, HEADER_FIELD.firstMiniFatSector);
41
+ const firstDifatSector = readU32(this.#buf, HEADER_FIELD.firstDifatSector);
42
+ const numDifatSectors = readU32(this.#buf, HEADER_FIELD.difatSectors);
44
43
  const fatSectorIds = this.#readDifat(numFatSectors, firstDifatSector, numDifatSectors);
45
44
  this.#fat = this.#readFat(fatSectorIds);
46
45
  this.#miniFat = this.#readChainValues(firstMiniFatSector);
@@ -50,28 +49,46 @@ export class CompoundFile {
50
49
  throw new VbaParseError('compound file has no root storage entry');
51
50
  this.#miniStream = this.#readViaFat(root.startSector, root.size);
52
51
  }
53
- names() {
54
- return this.#dir
55
- .filter((e) => e.type === TYPE_STREAM || e.type === TYPE_STORAGE)
56
- .map((e) => e.name);
57
- }
58
- readStream(name) {
59
- const entry = this.#dir.find((e) => e.type === TYPE_STREAM && e.name === name);
60
- if (!entry)
61
- return undefined;
62
- return this.#readEntryData(entry);
52
+ readStream(path) {
53
+ const rootIdx = this.#rootIndex();
54
+ const seen = new Set([rootIdx]);
55
+ let firstChild = this.#dir[rootIdx].child;
56
+ for (const [depth, segment] of path.entries()) {
57
+ const stream = depth === path.length - 1;
58
+ const entry = this.#childIndices(firstChild, seen, depth)
59
+ .map((idx) => this.#dir[idx])
60
+ .find((e) => (stream ? e.type === TYPE_STREAM : e.type === TYPE_STORAGE) &&
61
+ sameEntryName(e.name, segment));
62
+ if (entry === undefined)
63
+ return undefined;
64
+ if (stream)
65
+ return this.#readEntryData(entry);
66
+ firstChild = entry.child;
67
+ }
68
+ return undefined;
63
69
  }
64
70
  tree() {
71
+ const rootIdx = this.#rootIndex();
72
+ return this.#buildSiblings(this.#dir[rootIdx].child, new Set([rootIdx]));
73
+ }
74
+ #rootIndex() {
65
75
  const rootIdx = this.#dir.findIndex((e) => e.type === TYPE_ROOT);
66
76
  if (rootIdx < 0)
67
77
  throw new VbaParseError('compound file has no root storage entry');
68
- const root = this.#dir[rootIdx];
69
- return this.#buildSiblings(root.child, new Set([rootIdx]));
78
+ return rootIdx;
70
79
  }
71
80
  #buildSiblings(firstChild, seen, depth = 0) {
72
81
  if (depth > MAX_TREE_DEPTH)
73
82
  throw new VbaParseError('directory tree nests too deep');
74
- const nodes = [];
83
+ return this.#childIndices(firstChild, seen, depth).map((idx) => {
84
+ const e = this.#dir[idx];
85
+ return e.type === TYPE_STORAGE
86
+ ? { name: e.name, children: this.#buildSiblings(e.child, seen, depth + 1) }
87
+ : { name: e.name, data: this.#readEntryData(e) };
88
+ });
89
+ }
90
+ #childIndices(firstChild, seen, depth) {
91
+ const indices = [];
75
92
  const walk = (idx, siblingDepth) => {
76
93
  if (idx >= MAX_REGULAR_SECTOR)
77
94
  return;
@@ -86,16 +103,11 @@ export class CompoundFile {
86
103
  if (e.type === TYPE_EMPTY)
87
104
  throw new VbaParseError('directory tree links an empty entry');
88
105
  walk(e.left, siblingDepth + 1);
89
- if (e.type === TYPE_STORAGE) {
90
- nodes.push({ name: e.name, children: this.#buildSiblings(e.child, seen, depth + 1) });
91
- }
92
- else {
93
- nodes.push({ name: e.name, data: this.#readEntryData(e) });
94
- }
106
+ indices.push(idx);
95
107
  walk(e.right, siblingDepth + 1);
96
108
  };
97
109
  walk(firstChild, depth);
98
- return nodes;
110
+ return indices;
99
111
  }
100
112
  #readEntryData(entry) {
101
113
  if (entry.size >= this.#miniCutoff)
@@ -105,8 +117,8 @@ export class CompoundFile {
105
117
  #readDifat(numFatSectors, firstDifat, numDifat) {
106
118
  const wanted = Math.min(numFatSectors, this.#maxSector);
107
119
  const ids = [];
108
- for (let i = 0; i < 109 && ids.length < wanted; i++) {
109
- const v = readU32(this.#buf, 76 + i * 4);
120
+ for (let i = 0; i < HEADER_DIFAT_SLOTS && ids.length < wanted; i++) {
121
+ const v = readU32(this.#buf, HEADER_FIELD.difat + i * 4);
110
122
  if (v >= MAX_REGULAR_SECTOR)
111
123
  break;
112
124
  ids.push(v);
@@ -163,16 +175,16 @@ export class CompoundFile {
163
175
  const raw = this.#readChainFull(firstDirSector);
164
176
  const entries = [];
165
177
  for (let off = 0; off + DIR_ENTRY_SIZE <= raw.length; off += DIR_ENTRY_SIZE) {
166
- const type = raw[off + 66];
178
+ const type = raw[off + DIR_ENTRY_FIELD.objectType];
167
179
  if (type !== TYPE_EMPTY &&
168
180
  type !== TYPE_STORAGE &&
169
181
  type !== TYPE_STREAM &&
170
182
  type !== TYPE_ROOT) {
171
183
  throw new VbaParseError(`directory entry has invalid object type ${type}`);
172
184
  }
173
- const left = readU32(raw, off + 68);
174
- const right = readU32(raw, off + 72);
175
- const child = readU32(raw, off + 76);
185
+ const left = readU32(raw, off + DIR_ENTRY_FIELD.leftSibling);
186
+ const right = readU32(raw, off + DIR_ENTRY_FIELD.rightSibling);
187
+ const child = readU32(raw, off + DIR_ENTRY_FIELD.child);
176
188
  if (type === TYPE_EMPTY) {
177
189
  entries.push({
178
190
  name: '',
@@ -185,14 +197,14 @@ export class CompoundFile {
185
197
  });
186
198
  continue;
187
199
  }
188
- const nameLen = readU16(raw, off + 64);
200
+ const nameLen = readU16(raw, off + DIR_ENTRY_FIELD.nameLength);
189
201
  if (nameLen > 64 || nameLen % 2 !== 0) {
190
202
  throw new VbaParseError(`directory entry has invalid name length ${nameLen}`);
191
203
  }
192
204
  const name = decodeUtf16le(raw.subarray(off, off + Math.max(0, nameLen - 2)));
193
- const startSector = readU32(raw, off + 116);
194
- const size = readU32(raw, off + 120);
195
- if (readU32(raw, off + 124) !== 0) {
205
+ const startSector = readU32(raw, off + DIR_ENTRY_FIELD.startSector);
206
+ const size = readU32(raw, off + DIR_ENTRY_FIELD.sizeLow);
207
+ if (readU32(raw, off + DIR_ENTRY_FIELD.sizeHigh) !== 0) {
196
208
  throw new VbaParseError(`directory entry ${quoted(name)} declares a stream larger than 4 GiB`);
197
209
  }
198
210
  entries.push({ name, type, startSector, size, left, right, child });
@@ -51,3 +51,9 @@ export interface DirRecord {
51
51
  * @throws {VbaParseError} when a record's payload runs past the end of the stream.
52
52
  */
53
53
  export declare function dirRecords(dir: Uint8Array, context: string): Generator<DirRecord>;
54
+ /**
55
+ * One record's bytes, `Id(u16) Size(u32) data[Size]`: the grammar {@link dirRecords} walks, written.
56
+ * Built as bytes rather than spread into a `number[]`, which overflowed the call stack on a payload of a
57
+ * few hundred thousand bytes. Not for PROJECTVERSION, whose uncounted VersionMinor no edit here writes.
58
+ */
59
+ export declare function dirRecord(id: number, data: Uint8Array): Uint8Array;
@@ -1,4 +1,5 @@
1
- import { readU16, readU32 } from './bytes.js';
1
+ import { hex } from '../hex.js';
2
+ import { readU16, readU32, writeU16, writeU32 } from './bytes.js';
2
3
  import { VbaParseError } from './errors.js';
3
4
  export const REC_PROJECT_CODEPAGE = 0x0003;
4
5
  export const REC_PROJECT_VERSION = 0x0009;
@@ -19,10 +20,17 @@ export function* dirRecords(dir, context) {
19
20
  const size = readU32(dir, pos + 2);
20
21
  const dataStart = pos + 6;
21
22
  if (dataStart + size > dir.length) {
22
- throw new VbaParseError(`dir record 0x${id.toString(16)} ${context}`);
23
+ throw new VbaParseError(`dir record 0x${hex(id, 4)} ${context}`);
23
24
  }
24
25
  const end = dataStart + size + (id === REC_PROJECT_VERSION ? 2 : 0);
25
26
  yield { id, recordStart: pos, dataStart, size, end };
26
27
  pos = end;
27
28
  }
28
29
  }
30
+ export function dirRecord(id, data) {
31
+ const out = new Uint8Array(6 + data.length);
32
+ writeU16(out, 0, id);
33
+ writeU32(out, 2, data.length);
34
+ out.set(data, 6);
35
+ return out;
36
+ }
@@ -12,10 +12,10 @@ export declare class VbaParseError extends XlsxError {
12
12
  readonly code = "malformed-input";
13
13
  }
14
14
  /**
15
- * Thrown when authoring a VBA project (synthesizing a `vbaProject.bin` from module source) is asked to
16
- * produce something that cannot be encoded to a well-formed container: a stream name longer than the
17
- * [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed the
18
- * writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
15
+ * Thrown when a structural edit of a VBA project (removing a module, adding a library reference) is
16
+ * asked to produce something that cannot be encoded to a well-formed container: a stream name longer
17
+ * than the [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed
18
+ * the writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
19
19
  * {@link VbaParseError} (which reports a malformed blob *read* from an untrusted file).
20
20
  *
21
21
  * It shares `code: 'authoring'` with `AuthoringError` rather than being one, and it is the only
@@ -1,5 +1,3 @@
1
- export { type CfbNode, type CfbStorage, type CfbStream, writeCompoundFile } from './cfb-writer.ts';
2
1
  export { VbaAuthorError, VbaParseError } from './errors.ts';
3
- export { compressContainer, decompressContainer } from './ms-ovba.ts';
4
2
  export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, type VbaModule, type VbaModuleKind, type VbaProject, type VbaProjectSignature, type VbaProjectSignatureKind, vbaProjectSignatureKind, } from './project.ts';
5
3
  export { addVbaReference, removeVbaModule, type VbaLibraryReference } from './project-editor.ts';
package/dist/vba/index.js CHANGED
@@ -1,5 +1,3 @@
1
- export { writeCompoundFile } from './cfb-writer.js';
2
1
  export { VbaAuthorError, VbaParseError } from './errors.js';
3
- export { compressContainer, decompressContainer } from './ms-ovba.js';
4
2
  export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, vbaProjectSignatureKind, } from './project.js';
5
3
  export { addVbaReference, removeVbaModule } from './project-editor.js';
@@ -6,7 +6,11 @@ export declare function decompressContainer(buf: Uint8Array, start?: number, max
6
6
  /**
7
7
  * Compress `data` into an MS-OVBA CompressedContainer: the inverse of {@link decompressContainer}.
8
8
  * Every 4096-decompressed-byte window is emitted as a compressed chunk of literal and copy tokens, or
9
- * stored verbatim when compression would not shrink it (so the encoded chunk never exceeds the 12-bit
10
- * size field). The result re-expands to `data` byte-for-byte.
9
+ * stored raw when those tokens would not fit the 4096 bytes a chunk's data may hold.
10
+ *
11
+ * The result re-expands to `data` byte-for-byte, with one exception the format itself prescribes: a
12
+ * final window that is shorter than 4096 bytes and does not compress (only possible past about 3640
13
+ * bytes, where a literal's flag-bit overhead first overflows the chunk) is stored raw and padded with
14
+ * `0x00` to 4096, so it re-expands with those zeros after it ([MS-OVBA] 2.4.1.3.10).
11
15
  */
12
16
  export declare function compressContainer(data: Uint8Array): Uint8Array;
@@ -1,7 +1,9 @@
1
+ import { hex } from '../hex.js';
1
2
  import { readU16 } from './bytes.js';
2
3
  import { VbaParseError } from './errors.js';
3
4
  const MAX_CHUNK_DECOMPRESSED = 4096;
4
5
  const CHUNK_SIGNATURE = 0b011 << 12;
6
+ const CHUNK_SIGNATURE_MASK = 0b111 << 12;
5
7
  const CHUNK_COMPRESSED_FLAG = 0x8000;
6
8
  const MAX_MATCH_CANDIDATES = 64;
7
9
  const HASH_BITS = 12;
@@ -12,7 +14,7 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
12
14
  throw new VbaParseError(`compressed container starts past end of stream (${start} >= ${buf.length})`);
13
15
  }
14
16
  if (buf[start] !== 0x01) {
15
- throw new VbaParseError(`compressed container must begin with a 0x01 signature byte, found 0x${(buf[start] ?? 0).toString(16)}`);
17
+ throw new VbaParseError(`compressed container must begin with a 0x01 signature byte, found 0x${hex(buf[start] ?? 0, 2)}`);
16
18
  }
17
19
  const out = new ByteSink(maxOutput);
18
20
  let pos = start + 1;
@@ -20,9 +22,9 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
20
22
  const header = readU16(buf, pos);
21
23
  pos += 2;
22
24
  const chunkDataSize = (header & 0x0fff) + 1;
23
- const compressed = (header & 0x8000) !== 0;
24
- if (((header >> 12) & 0x7) !== 0b011) {
25
- throw new VbaParseError(`chunk header has a bad 0b011 signature (0x${header.toString(16)})`);
25
+ const compressed = (header & CHUNK_COMPRESSED_FLAG) !== 0;
26
+ if ((header & CHUNK_SIGNATURE_MASK) !== CHUNK_SIGNATURE) {
27
+ throw new VbaParseError(`chunk header has a bad 0b011 signature (0x${hex(header, 4)})`);
26
28
  }
27
29
  const chunkEnd = pos + chunkDataSize;
28
30
  if (chunkEnd > buf.length) {
@@ -108,8 +110,8 @@ export function compressContainer(data) {
108
110
  for (let start = 0; start < data.length; start += MAX_CHUNK_DECOMPRESSED) {
109
111
  const chunk = data.subarray(start, Math.min(start + MAX_CHUNK_DECOMPRESSED, data.length));
110
112
  const tokens = compressChunk(chunk);
111
- const compressed = tokens.length < chunk.length;
112
- const body = compressed ? tokens : chunk;
113
+ const compressed = tokens.length <= MAX_CHUNK_DECOMPRESSED;
114
+ const body = compressed ? tokens : padToChunk(chunk);
113
115
  const header = (compressed ? CHUNK_COMPRESSED_FLAG : 0) | CHUNK_SIGNATURE | ((body.length - 1) & 0x0fff);
114
116
  out.push(header & 0xff);
115
117
  out.push((header >> 8) & 0xff);
@@ -117,6 +119,13 @@ export function compressContainer(data) {
117
119
  }
118
120
  return out.bytes();
119
121
  }
122
+ function padToChunk(chunk) {
123
+ if (chunk.length === MAX_CHUNK_DECOMPRESSED)
124
+ return chunk;
125
+ const padded = new Uint8Array(MAX_CHUNK_DECOMPRESSED);
126
+ padded.set(chunk);
127
+ return padded;
128
+ }
120
129
  function compressChunk(chunk) {
121
130
  const tokens = new ByteSink();
122
131
  const heads = new Int32Array(1 << HASH_BITS).fill(NO_CANDIDATE);