doc-codec 2.5.0 → 2.7.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 (42) hide show
  1. package/README.md +38 -29
  2. package/dist/base64.cjs +48 -0
  3. package/dist/base64.d.cts +5 -0
  4. package/dist/base64.d.ts +5 -0
  5. package/dist/base64.js +46 -0
  6. package/dist/data-stream-BLCj4-V1.d.cts +11 -0
  7. package/dist/data-stream-BLCj4-V1.d.ts +11 -0
  8. package/dist/data-stream.cjs +25 -0
  9. package/dist/data-stream.d.cts +2 -0
  10. package/dist/data-stream.d.ts +2 -0
  11. package/dist/data-stream.js +24 -0
  12. package/dist/encryption.cjs +44 -10
  13. package/dist/encryption.d.cts +2 -2
  14. package/dist/encryption.d.ts +2 -2
  15. package/dist/encryption.js +45 -11
  16. package/dist/index.cjs +5 -0
  17. package/dist/index.d.cts +3 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +3 -1
  20. package/dist/pictures-write.cjs +85 -0
  21. package/dist/pictures-write.d.cts +12 -0
  22. package/dist/pictures-write.d.ts +12 -0
  23. package/dist/pictures-write.js +84 -0
  24. package/dist/pictures.cjs +3 -16
  25. package/dist/pictures.js +3 -16
  26. package/dist/prop/sep-write.cjs +16 -9
  27. package/dist/prop/sep-write.d.cts +2 -2
  28. package/dist/prop/sep-write.d.ts +2 -2
  29. package/dist/prop/sep-write.js +16 -9
  30. package/dist/read.cjs +1 -2
  31. package/dist/read.js +2 -3
  32. package/dist/table/write.cjs +28 -3
  33. package/dist/table/write.d.cts +2 -2
  34. package/dist/table/write.d.ts +2 -2
  35. package/dist/table/write.js +28 -3
  36. package/dist/{write-SN_aIwyF.d.ts → write-CMB3qF4i.d.cts} +9 -3
  37. package/dist/{write-SN_aIwyF.d.cts → write-t4rFgnA-.d.ts} +9 -3
  38. package/dist/write.cjs +65 -17
  39. package/dist/write.d.cts +1 -1
  40. package/dist/write.d.ts +1 -1
  41. package/dist/write.js +66 -18
  42. package/package.json +2 -2
package/README.md CHANGED
@@ -22,7 +22,7 @@ Built and shipped, on the read side:
22
22
  - **`readDocContent`** — the whole chain, producing a `'wordprocessing'` `ContentDocument` of paragraphs, runs and tables.
23
23
  - **`isDocBytes`** — distinguishes a `.doc` from the `.xls`, `.ppt` and OLE embeddings that share its container, by looking for a `WordDocument` stream carrying `FibBase.wIdent`.
24
24
  - **Document metadata** — `title`/`subject`/`author`/`keywords`/`createdIso`/`modifiedIso` read from a `"\x05SummaryInformation"` stream when one is present (see [Metadata](#metadata)); `comments` and `lastPrintedIso` remain unread, since `LayoutMetadata` has no field for either.
25
- - **RC4-encrypted documents, decrypted for real (ExaDev/documents.js#1113)** — `readDocContent`/`readDocStreams` take an optional `password`, verified against the Table stream's own `EncryptionHeader` before anything is trusted (see [Encryption](#encryption)). XOR obfuscation and RC4 CryptoAPI stay refused, tracked on [ExaDev/documents.js#922](https://github.com/ExaDev/documents.js/issues/922).
25
+ - **RC4-encrypted and XOR-obfuscated documents, decrypted for real (ExaDev/documents.js#1113, #922)** — `readDocContent`/`readDocStreams` take an optional `password`, verified against the Table stream's own `EncryptionHeader` for RC4, or against `FibBase.lKey` directly for XOR obfuscation (see [Encryption](#encryption)). RC4 CryptoAPI stays refused.
26
26
  - **Numbering definitions** — `readDocContent`'s own `numbering` field: every list's glyph/format, level-text template, and start-at value, resolved from `PlfLst`/`PlfLfo` and keyed by the same `listId` a paragraph's `ContentParagraph.list.numId` already carries. See [Numbering definitions](#numbering-definitions), which also covers `writeDocContent`'s own inverse.
27
27
  - **Every section's own page size and margins** — `prop/sep.ts`'s `readAllSectionProperties` resolves every `Sed` `PlcfSed` carries, each into its own `ContentSection.pageSize`/`.margins` (`sprmSXaPage`/`sprmSYaPage`/`sprmSDxaLeft`/`sprmSDxaRight`/`sprmSDyaTop`/`sprmSDyaBottom`), falling back field by field to Word's own new-document default (US Letter, one-inch margins) for a document carrying no `PlcfSed` at all, or for any one sprm a section leaves unstated. `read.ts`'s `splitIntoSections` groups the main document's own paragraphs by which section each falls in, per [MS-DOC] 2.8.26's own "each CP specifies the beginning of a range of text ... that constitutes a section" — a genuinely multi-section `.doc` now reads as multiple `ContentSection`s, each with its own real page geometry, rather than the whole document collapsing onto the first section's.
28
28
  - **Footnotes, endnotes, and comments, as plain text** — `notes.ts`'s `readNoteBodies` resolves `PlcffndTxt`/`PlcfendTxt`/`PlcfandTxt` (each a CP-only plex dividing its own subdocument into stories, [MS-DOC]'s own "each CP ... specifies the beginning of a story ... the story ends immediately prior to the next CP") into `DocContent.footnotes`/`.endnotes`/`.comments`: one `{id, text}`/`{id, text}` entry per story, `id` a synthetic one-based ordinal, matching the same simplification `ooxml.js`'s own `DocxDocument.footnotes`/`.endnotes`/`.comments` already make for the identical constructs (plain text, no paragraph/run structure). A story's own trailing guard paragraph mark ("not considered part of the story contents", [MS-DOC]'s own words) is excluded from its text; a genuinely empty story reads as `""`.
@@ -31,24 +31,23 @@ Built and shipped, on the read side:
31
31
 
32
32
  Built and shipped, on the write side — see [Writing](#writing) for the full scope statement:
33
33
 
34
- - **`writeDocContent`** — a `'wordprocessing'` `ContentDocument` (one section, paragraphs of runs and tables) to genuine [MS-DOC] bytes: a real piece table, real `ChpxFkp`/`PapxFkp` pages (splitting across as many as a document's own formatting needs, not just the common one-page case), a spec-conformant empty style sheet, a font table when a run names one, real `PlfLst`/`PlfLfo` numbering tables when at least one paragraph carries `list` (see [Numbering definitions](#numbering-definitions)), a real `PlcfSed`/`Sepx` stating the one section's own page size and margins, and a `"\x05SummaryInformation"` stream when the input's metadata carries anything that stream can hold (see [Metadata](#metadata)) — wrapped in a real [MS-CFB] compound file via `archive-codec`'s `writeCompoundFile`. A `ContentTable` block is expanded by `table/write.ts`'s `flattenSectionBlocks` into the same flat paragraph sequence every other block already is (see [Tables](#tables)), so table paragraphs flow through the identical `ChpxFkp`/`PapxFkp` paging as every other paragraph rather than a separate table-only path. Each cell's own `background` and `borders` ride along into the row's TAP (see [Cell decoration](#cell-decoration)).
34
+ - **`writeDocContent`** — a `'wordprocessing'` `ContentDocument` (any number of sections, each of paragraphs, tables, and inline images) to genuine [MS-DOC] bytes: a real piece table, real `ChpxFkp`/`PapxFkp` pages (splitting across as many as a document's own formatting needs, not just the common one-page case), a real style sheet minting one entry per distinct `styleId`/`headingLevel` a document's paragraphs actually use (`style/stsh.ts`'s `buildStshForStyles`, [ExaDev/documents.js#1059](https://github.com/ExaDev/documents.js/issues/1059)), a font table when a run names one, real `PlfLst`/`PlfLfo` numbering tables when at least one paragraph carries `list` (see [Numbering definitions](#numbering-definitions)), a real `PlcfSed`/`Sepx` array stating every section's own page size and margins (`ExaDev/documents.js#971`, see [Sections](#sections)), a `"Data"` stream carrying every inline picture's own real `PICF`/`OfficeArtInlineSpContainer`/blip bytes (`ExaDev/documents.js#971`, see [Images](#images)), and a `"\x05SummaryInformation"` stream when the input's metadata carries anything that stream can hold (see [Metadata](#metadata)) — wrapped in a real [MS-CFB] compound file via `archive-codec`'s `writeCompoundFile`. A `ContentTable` block is expanded by `table/write.ts`'s `flattenSectionBlocks` into the same flat paragraph sequence every other block already is (see [Tables](#tables)), so table paragraphs flow through the identical `ChpxFkp`/`PapxFkp` paging as every other paragraph rather than a separate table-only path. Each cell's own `background` and `borders` ride along into the row's TAP (see [Cell decoration](#cell-decoration)).
35
35
  - Every property `writeDocContent` writes is verified by reading it back through this package's own `readDocContent` (`src/write.test.ts`), and additionally against a real, independent [MS-DOC] implementation: LibreOffice opened, rendered, and re-exported a `writeDocContent` sample without error or content loss, including bold/italic/underline/strike/size/colour/font-family runs, paragraph alignment and indentation, non-Latin-1 and non-BMP text (accented Latin, CJK, an emoji surrogate pair), a section's own page size and all four margins (a non-default 600x800pt page with a 90/54/45/36pt left/right/top/bottom margin, confirmed against LibreOffice's own `fo:page-width`/`fo:page-height`/`fo:margin-*` export to the exact point), and a table — recognised as a genuine `table:table`, its row/column/cell structure and both horizontal and vertical merges intact, matching real `table:number-columns-spanned`/`table:number-rows-spanned` attributes and `table:covered-table-cell` elements, and each cell's own background fill and per-side borders recovered with their exact colours, exactly as [Tables](#tables) confirms in full.
36
36
 
37
37
  **Not built, and not approximated, on either side.** Each of these is a genuine layer of [MS-DOC] that this package does not implement; none is silently faked, and a document using one reads (or fails to write) as though it did not:
38
38
 
39
- | Absent | Consequence |
40
- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
41
- | **Nested tables, on write** | `readDocContent` recurses into a table nested inside a table cell to whatever depth the file states (see the read-side Tables bullet above and [Tables](#tables)). `writeDocContent` still refuses a `ContentTable` block found inside a table cell's own blocks: it has no depth-aware sgc-1/sgc-5 grpprl encoding for anything past depth 1, and writing one incorrectly would silently corrupt the nested table's own structure rather than fail loudly. |
42
- | **Floating/anchored drawn objects, text boxes, and every picture format beyond JPEG/PNG** | An inline picture (`U+0001`) resolves to a real image when its own blip is `OfficeArtBlipJPEG`/`OfficeArtBlipPNG` (see the read-side Inline pictures bullet above and [Images](#images)). A floating or anchored drawn object (`U+0008`, `PlcfSpa`) is a genuinely different structure — an arbitrary `OfficeArtSpContainer` shape (which may or may not be a picture at all), reached through a document-level drawing tree (`OfficeArtDgContainer`) rather than a single inline blip — and is not resolved at all; its anchor character is dropped. A text box rides the identical drawing layer (`PlcfTxbxTxt`, itself anchored via a drawn object), so it is likewise not read. A blip format other than JPEG/PNG (a WMF/EMF/PICT metafile, a raw DIB, TIFF) is a structure this package's own image schema (`png`/`jpeg`/`svg`/`gif` only) has nowhere to hold without re-encoding pixels this package has no image codec to perform, so `readInlinePicture` returns undefined for one rather than mis-decoding it, and the anchor drops exactly as an unresolved floating object's does. `writeDocContent` refuses an image block outright, on either side of this line. |
43
- | **A style's own formatting on write** | A style's own property sets (`STD.grLPUpxSw`) are resolved and folded on read ([ExaDev/documents.js#1005](https://github.com/ExaDev/documents.js/issues/1005)): a paragraph's formatting is its style's own grpprlPapx/grpprlChpx (walked up the full `istdBase` inheritance chain, most-specific style winning) plus its own direct exceptions on top, and a run's own referenced character style (`sprmCIstd`) folds in the same way. `writeDocContent` mints a real STSH entry for every distinct `styleId`/`headingLevel` a document's paragraphs use ([ExaDev/documents.js#1059](https://github.com/ExaDev/documents.js/issues/1059)), so style IDENTITY round-trips -- but every minted entry carries an empty `grLPUpxSw` (no formatting of its own), since doc-codec's flat `ContentDocument` has no style-vs-direct-formatting split to draw a real one from: every property this writer emits is already, unconditionally, a direct exception. A style's own inherited formatting therefore never round-trips through a write -- only its name and istd position do. |
44
- | **Table and numbering style formatting** | `STD.grLPUpxSw` is resolved only for paragraph and character styles (`stk` 1/2, see the row above) -- a table style's `StkTableGRLPUPX` (its own `TAPX` plus `CNFC`-conditional per-region formatting, [MS-DOC] 2.4.6.6's own table-specific steps 6-9) and a numbering style's `StkListGRLPUPX` are neither read nor written. A table applying a named table style, or a paragraph using a numbering style rather than direct list formatting, reports none of the formatting either would supply. |
45
- | **Subdocuments, on write; text boxes, on either side** | Footnotes, endnotes, comments, and headers/footers are now read (see the three read-side bullets above); none is written — `writeDocContent` still only ever produces a main document with `ccpFtn`/`ccpHdd`/`ccpAtn`/`ccpEdn` all zero, and a `ContentDocument` carries nowhere on its own `ContentSection.blocks` to state a header/footer's own content distinctly from the body's, which a real inverse would need. Text boxes are not read or written in either direction: `PlcfTxbxTxt` divides its own subdocument into stories the identical way the note-text plexes above do, but a text box's own anchor rides the drawn-object mechanism ([Images](#images)'s own scope note), which this package does not resolve. |
46
- | **Multiple sections, on write** | `readDocContent` resolves every section `PlcfSed` states, each with its own page size and margins (see the read-side bullet above). `writeDocContent` still refuses a `ContentDocument` with more than one section, rather than silently merging their content into what would read back as one — writing a real multi-section `PlcfSed`/`Sepx` array is unimplemented on the write side. |
47
- | **Extended and user-defined document properties** | `title`/`subject`/`author`/`keywords`/`createdIso`/`modifiedIso` are read from and written to a `"\x05SummaryInformation"` stream when present (see [Metadata](#metadata)); the sibling `"\x05DocumentSummaryInformation"` stream (company, manager, and custom user-defined properties) is not read or written at all. |
48
- | **Encryption** | RC4-encrypted documents ([MS-DOC] 2.2.6.2, ExaDev/documents.js#1113) are decrypted given a password -- `readDocContent`/`readDocStreams` take an optional `password`, verified against the Table stream's own EncryptionHeader before anything is trusted (see [Encryption](#encryption) below). XOR obfuscation and RC4 CryptoAPI (a different, newer EncryptionHeader shape) still refuse with a `DocUnsupportedError` rather than being read as plaintext or misread as the wrong scheme. `writeDocContent` never encrypts. |
49
- | **`sprmPHugePapx` / `sprmPTableProps`** | Paragraph properties stored indirectly in the Data stream are not followed, so such a paragraph reads with fewer properties than it states. [MS-DOC] 2.4.3's own Overview of Tables text names `sprmPTableProps` as a real, legal alternative to `sprmTDefTable` some applications process — but a real producer's row mark is not shown to prefer it: a genuine LibreOffice-authored `.doc` table's own row mark states its TAP through the identical direct `sprmTDefTable` this package's reader and writer already use (confirmed by parsing a LibreOffice 26.2.5.2-authored table's raw `PapxFkp` bytes; see [ExaDev/documents.js#892](https://github.com/ExaDev/documents.js/issues/892)), matching 2.4.3's own compatibility guidance ("An application SHOULD use sprmTDefTable to define table cells for applications that do not process sprmPTableProps"). `writeDocContent` never writes an indirect Papx. |
50
- | **Hyperlinks and fields** | `ContentRun.hyperlink`, footnote/comment/annotation references, and every other field or anchor character are read as plain text or dropped (see [What is converted](#what-is-converted)) and are not written. |
51
- | **Every FIB field beyond what this package's own reader needs** | `writeDocContent` populates only the fc/lcb pairs its own reader consults (the style sheet, the two property bin tables, the Clx, the font table). Roughly 140 other `FibRgFcLcb97` pairs — `SttbfAssoc`, `Dop`, the printer-driver structures among them — are left zero, which is the format's own "undefined, MUST be ignored" contract for most of them, but not a certification that every third-party [MS-DOC] reader accepts the result; see `fib/write.ts`'s own note. |
39
+ | Absent | Consequence |
40
+ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
41
+ | **Nested tables, on write** | `readDocContent` recurses into a table nested inside a table cell to whatever depth the file states (see the read-side Tables bullet above and [Tables](#tables)). `writeDocContent` still refuses a `ContentTable` block found inside a table cell's own blocks: it has no depth-aware sgc-1/sgc-5 grpprl encoding for anything past depth 1, and writing one incorrectly would silently corrupt the nested table's own structure rather than fail loudly. |
42
+ | **Floating/anchored drawn objects, text boxes, and every picture format beyond JPEG/PNG** | An inline picture (`U+0001`) round-trips when its own blip is `OfficeArtBlipJPEG`/`OfficeArtBlipPNG` (see the read-side Inline pictures bullet above, `writeDocContent`'s own `Data`-stream bullet, and [Images](#images) — `ExaDev/documents.js#971`). A floating or anchored drawn object (`U+0008`, `PlcfSpa`) is a genuinely different structure — an arbitrary `OfficeArtSpContainer` shape (which may or may not be a picture at all), reached through a document-level drawing tree (`OfficeArtDgContainer`) rather than a single inline blip — and is not resolved on read, nor written: `document-schema.js` has no block kind for a floating/anchored image distinct from an inline `ContentImageBlock` in the first place. A text box rides the identical drawing layer (`PlcfTxbxTxt`, itself anchored via a drawn object), so it is likewise not read or written. `ContentImageBlock.format`'s `svg`/`gif` members, and any blip format other than JPEG/PNG on read (a WMF/EMF/PICT metafile, a raw DIB, TIFF), have no `OfficeArtBlip` type this package's own writer can fabricate or its own reader can decode, so `readInlinePicture` returns `undefined` for one rather than mis-decoding it, and `writeDocContent` throws `DocUnsupportedError` naming the unwritable format rather than fabricating a blip its own reader could not read back. |
43
+ | **A style's own formatting on write** | A style's own property sets (`STD.grLPUpxSw`) are resolved and folded on read ([ExaDev/documents.js#1005](https://github.com/ExaDev/documents.js/issues/1005)): a paragraph's formatting is its style's own grpprlPapx/grpprlChpx (walked up the full `istdBase` inheritance chain, most-specific style winning) plus its own direct exceptions on top, and a run's own referenced character style (`sprmCIstd`) folds in the same way. `writeDocContent` mints a real STSH entry for every distinct `styleId`/`headingLevel` a document's paragraphs use ([ExaDev/documents.js#1059](https://github.com/ExaDev/documents.js/issues/1059)), so style IDENTITY round-trips -- but every minted entry carries an empty `grLPUpxSw` (no formatting of its own), since doc-codec's flat `ContentDocument` has no style-vs-direct-formatting split to draw a real one from: every property this writer emits is already, unconditionally, a direct exception. A style's own inherited formatting therefore never round-trips through a write -- only its name and istd position do. |
44
+ | **Table and numbering style formatting** | `STD.grLPUpxSw` is resolved only for paragraph and character styles (`stk` 1/2, see the row above) -- a table style's `StkTableGRLPUPX` (its own `TAPX` plus `CNFC`-conditional per-region formatting, [MS-DOC] 2.4.6.6's own table-specific steps 6-9) and a numbering style's `StkListGRLPUPX` are neither read nor written. A table applying a named table style, or a paragraph using a numbering style rather than direct list formatting, reports none of the formatting either would supply. |
45
+ | **Subdocuments, on write; text boxes, on either side** | Footnotes, endnotes, comments, and headers/footers are now read (see the three read-side bullets above); none is written — `writeDocContent` still only ever produces a main document with `ccpFtn`/`ccpHdd`/`ccpAtn`/`ccpEdn` all zero, and a `ContentDocument` carries nowhere on its own `ContentSection.blocks` to state a header/footer's own content distinctly from the body's, which a real inverse would need. Text boxes are not read or written in either direction: `PlcfTxbxTxt` divides its own subdocument into stories the identical way the note-text plexes above do, but a text box's own anchor rides the drawn-object mechanism ([Images](#images)'s own scope note), which this package does not resolve. |
46
+ | **Extended and user-defined document properties** | `title`/`subject`/`author`/`keywords`/`createdIso`/`modifiedIso` are read from and written to a `"\x05SummaryInformation"` stream when present (see [Metadata](#metadata)); the sibling `"\x05DocumentSummaryInformation"` stream (company, manager, and custom user-defined properties) is not read or written at all. |
47
+ | **Encryption** | RC4-encrypted ([MS-DOC] 2.2.6.2, ExaDev/documents.js#1113) and XOR-obfuscated (2.2.6.1, #922) documents are both decrypted given a password -- `readDocContent`/`readDocStreams` take an optional `password`, verified against the Table stream's own EncryptionHeader for RC4, or against `FibBase.lKey` directly for XOR obfuscation (see [Encryption](#encryption) below). RC4 CryptoAPI (a different, newer EncryptionHeader shape) still refuses with a `DocUnsupportedError` rather than being read as plaintext or misread as the wrong scheme. `writeDocContent` never encrypts. |
48
+ | **`sprmPHugePapx` / `sprmPTableProps`** | Paragraph properties stored indirectly in the Data stream are not followed, so such a paragraph reads with fewer properties than it states. [MS-DOC] 2.4.3's own Overview of Tables text names `sprmPTableProps` as a real, legal alternative to `sprmTDefTable` some applications process but a real producer's row mark is not shown to prefer it: a genuine LibreOffice-authored `.doc` table's own row mark states its TAP through the identical direct `sprmTDefTable` this package's reader and writer already use (confirmed by parsing a LibreOffice 26.2.5.2-authored table's raw `PapxFkp` bytes; see [ExaDev/documents.js#892](https://github.com/ExaDev/documents.js/issues/892)), matching 2.4.3's own compatibility guidance ("An application SHOULD use sprmTDefTable to define table cells for applications that do not process sprmPTableProps"). `writeDocContent` never writes an indirect Papx. |
49
+ | **Hyperlinks and fields** | `ContentRun.hyperlink`, footnote/comment/annotation references, and every other field or anchor character are read as plain text or dropped (see [What is converted](#what-is-converted)) and are not written. |
50
+ | **Every FIB field beyond what this package's own reader needs** | `writeDocContent` populates only the fc/lcb pairs its own reader consults (the style sheet, the two property bin tables, the Clx, the font table). Roughly 140 other `FibRgFcLcb97` pairs `SttbfAssoc`, `Dop`, the printer-driver structures among them are left zero, which is the format's own "undefined, MUST be ignored" contract for most of them, but not a certification that every third-party [MS-DOC] reader accepts the result; see `fib/write.ts`'s own note. |
52
51
 
53
52
  One construct is refused rather than mis-read: a `sprmPChgTabs` whose `cb` is the `255` sentinel encodes its own length as a formula over tab-stop counts this package does not parse, and its length is needed to find the next `Prl`. Rather than guess and silently mis-read every property after it, `operandSize` throws.
54
53
 
@@ -165,7 +164,9 @@ A paragraph's own `list.numId`/`list.level` (`sprmPIlfo`/`sprmPIlvl`, unchanged
165
164
 
166
165
  [MS-DOC] 2.8.26's `PlcfSed` divides the main document's own text into sections the identical way `Plcfhdd`/`PlcffndTxt`/`PlcfandTxt`/`PlcfendTxt` (below) divide their own subdocuments into stories: an array of CPs, each naming where the next section's text begins, "the range of text ends immediately prior to the next CP." `prop/sep.ts`'s `readAllSectionProperties` resolves every `Sed` this way, in document order, each into its own page width/height and four margins via `applySectionSprms` (`sprmSXaPage`/`sprmSYaPage`/`sprmSDxaLeft`/`sprmSDxaRight`/`sprmSDyaTop`/`sprmSDyaBottom`); `read.ts`'s `splitIntoSections` then walks the main document's own flat paragraph sequence once, advancing to the next section exactly when a paragraph's own end CP matches the next section's start CP — the paragraph carrying the end-of-section character (`0x000C`) itself, per [MS-DOC]'s own "there MUST also be an end-of-section character... as the final character in the text range of all but the last section." A file with no `PlcfSed` at all (`lcbPlcfSed` 0) reads as a single section falling back, field by field, to Word's own new-document default (US Letter, one-inch margins) — the identical fallback a `PlcfSed` that leaves one sprm unstated already used before multi-section support existed.
167
166
 
168
- Only page size and the four margins are resolved, matching what `document-schema.js`'s own `ContentSection` (`pageSize` + `margins`) has anywhere to hold; every other section sprm (columns, headers/footers distance, vertical justification, page borders, line numbering, and the rest of the roughly seventy [MS-DOC] 2.6.4 names) is absent rather than present-and-ignored, the identical documented-gap convention the paragraph- and character-property tables already use. `writeDocContent` still only ever writes one section (see [Writing](#writing)); a real multi-section write is unimplemented.
167
+ Only page size and the four margins are resolved, matching what `document-schema.js`'s own `ContentSection` (`pageSize` + `margins`) has anywhere to hold; every other section sprm (columns, headers/footers distance, vertical justification, page borders, line numbering, and the rest of the roughly seventy [MS-DOC] 2.6.4 names) is absent rather than present-and-ignored, the identical documented-gap convention the paragraph- and character-property tables already use. `ContentSection.breakType` (how a section begins relative to the one before it -- `nextPage`/`continuous`/`evenPage`/`oddPage`) is likewise neither read nor written: [MS-DOC]'s own `sprmSBkc` is a genuinely different sprm from the six this reader already resolves, and nothing here decodes or encodes it yet.
168
+
169
+ `writeDocContent` writes every section a `ContentDocument` carries, not only the first (`ExaDev/documents.js#971`): `prop/sep-write.ts`'s `buildPlcfSed` places one real `Sed`/`Sepx` pair per section, each stating that section's own `pageSize`/`margins` via `encodeSectionGrpprl`'s inverse of `applySectionSprms` above, and `write.ts`'s own per-section loop closes every section but the last on a genuine end-of-section character (`0x000C`) -- never a table's own row-ending mark, the identical guarantee [Writing](#writing) states for the Main Document's own final character, applied at every section boundary in between. `breakType` is not written either, matching the read side.
169
170
 
170
171
  ## Subdocuments
171
172
 
@@ -187,7 +188,9 @@ An inline picture is a character with Unicode value `U+0001` ("A picture locatio
187
188
 
188
189
  **A paragraph is split around a resolved (or attempted) picture, never left as a run.** An inline image is block-level in `document-schema.js`'s own model, so `text/paragraphs.ts`'s `buildParagraphBlocks` splits the paragraph's own text at each `U+0001` anchor into however many `ContentBlock`s the anchors demand — a leading paragraph, the image, a trailing paragraph, and so on for a paragraph carrying more than one — every split paragraph piece keeping the whole paragraph's own alignment/spacing/list attributes unchanged, mirroring how `ooxml.js`'s own docx reader already splits a paragraph around a mid-run page break. A paragraph containing nothing but one unresolved picture anchor (an unsupported blip format, or no `"Data"` stream at all) collapses to the identical single empty `ContentParagraph` a plain anchor-only paragraph always produced; a paragraph that resolves to at least one real block (text, an image) never gains a synthetic empty wrapper alongside it.
189
190
 
190
- **Not read: floating/anchored drawn objects and text boxes.** A floating picture is a different anchor entirely — `U+0008` ("A drawn object. See plcfSpa"), referenced by a `PlcfSpa` (`SPA` records, 26 bytes each, one `spid` per anchor) that names a shape inside a document-level `OfficeArtDgContainer` drawing tree rather than a single inline blip a `Chpx` sprm points at directly. That shape need not even be a picture — an arbitrary `OfficeArtSpContainer` (autoshape, connector, text box) — so resolving one properly means walking the whole drawing tree, not decoding one more blip kind; this package does not attempt it, and the anchor character drops exactly as any other unresolved anchor does. `writeDocContent` refuses an image block outright, on either side of this line.
191
+ **Not read: floating/anchored drawn objects and text boxes.** A floating picture is a different anchor entirely — `U+0008` ("A drawn object. See plcfSpa"), referenced by a `PlcfSpa` (`SPA` records, 26 bytes each, one `spid` per anchor) that names a shape inside a document-level `OfficeArtDgContainer` drawing tree rather than a single inline blip a `Chpx` sprm points at directly. That shape need not even be a picture — an arbitrary `OfficeArtSpContainer` (autoshape, connector, text box) — so resolving one properly means walking the whole drawing tree, not decoding one more blip kind; this package does not attempt it, and the anchor character drops exactly as any other unresolved anchor does.
192
+
193
+ **Written, for PNG and JPEG (`ExaDev/documents.js#971`).** `pictures-write.ts`'s `buildInlinePicture` is `readInlinePicture`'s own inverse: a `ContentImageBlock` becomes the identical `PICF` (68 bytes, `mfpf.mm` always `MM_SHAPE` — this writer never emits `MM_SHAPEFILE`'s own source-filename pair) followed by an empty `OfficeArtInlineSpContainer.shape` (a bare 8-byte record header, `recLen` 0 — this writer states no shape properties of its own, matching that the reader never looks inside it either) and a single-`rgbUid` `OfficeArtBlip` record (`OfficeArtBlipPNG`/`OfficeArtBlipJPEG`, `recInstance` `0x06E0`/`0x046A`) wrapping the image's own raw file bytes verbatim, with `PICMID.dxaGoal`/`.dyaGoal` stating the image's own real size in twips and `mx`/`my` always `1000` ("no scaling"). `table/write.ts`'s `flattenSectionBlocks` places each `ContentImageBlock` as its own one-run paragraph carrying a `U+0001` anchor and `sprmCPicLocation`, exactly matching what `buildParagraphBlocks` (above) splits back out on the next read; `data-stream.ts`'s `DataStreamBuilder` accumulates every picture's own bytes across the whole document into one real `"Data"` stream, shared across every section, only added to the compound file at all when the document carries at least one picture. `svg`/`gif`, and any format beyond PNG/JPEG, throw `DocUnsupportedError` rather than fabricating a blip this package's own reader could not decode back (see the read-side scope row above).
191
194
 
192
195
  ## Metadata
193
196
 
@@ -204,7 +207,7 @@ Only the fixed SummaryInformation property set is read or written — the siblin
204
207
 
205
208
  ## Encryption
206
209
 
207
- A `.doc` protected with a password to open uses one of three schemes [MS-DOC] 2.2.6 names, selected by `FibBase`'s own `fEncrypted`/`fObfuscated` flags: RC4 encryption ([MS-DOC] 2.2.6.2, this reader), XOR obfuscation (2.2.6.1), or RC4 CryptoAPI (2.2.6.3, a different, newer `EncryptionHeader` shape). `readDocContent`/`readDocStreams` take an optional `password`, ignored for an unencrypted document; a missing or incorrect password against an RC4-encrypted one throws `DocUnsupportedError` rather than returning a partial or garbled document, and so does an XOR-obfuscated or RC4-CryptoAPI-encrypted one regardless of password, since neither is implemented.
210
+ A `.doc` protected with a password to open uses one of three schemes [MS-DOC] 2.2.6 names, selected by `FibBase`'s own `fEncrypted`/`fObfuscated` flags: RC4 encryption ([MS-DOC] 2.2.6.2, this reader), XOR obfuscation (2.2.6.1, this reader), or RC4 CryptoAPI (2.2.6.3, a different, newer `EncryptionHeader` shape). `readDocContent`/`readDocStreams` take an optional `password`, ignored for an unencrypted document; a missing or incorrect password against an encrypted or obfuscated one throws `DocUnsupportedError` rather than returning a partial or garbled document, and so does an RC4-CryptoAPI-encrypted one regardless of password, since that scheme alone is not implemented.
208
211
 
209
212
  ```ts
210
213
  import { readDocContent } from "doc-codec";
@@ -212,28 +215,28 @@ import { readDocContent } from "doc-codec";
212
215
  const document = readDocContent(bytes, "correct horse battery staple");
213
216
  ```
214
217
 
215
- [MS-DOC] 2.2.6.2's own `EncryptionHeader` _is_ [MS-OFFCRYPTO] 2.3.6.1's RC4 encryption header — byte-identical to what `xls-codec`'s own `FilePass` record carries (ExaDev/documents.js#1108), so `src/encryption.ts` needs no new cryptography, only `archive-codec`'s shared `deriveOfficeRc4BaseHash`/`decryptOfficeRc4` primitives and the doc-specific container layout, which differs from BIFF8's in three real ways rather than being a copy of xls-codec's own scheme:
218
+ [MS-DOC] 2.2.6.2's own `EncryptionHeader` _is_ [MS-OFFCRYPTO] 2.3.6.1's RC4 encryption header — byte-identical to what `xls-codec`'s own `FilePass` record carries (ExaDev/documents.js#1108), so `src/encryption.ts` needs no new cryptography for RC4, only `archive-codec`'s shared `deriveOfficeRc4BaseHash`/`decryptOfficeRc4` primitives and the doc-specific container layout, which differs from BIFF8's in three real ways rather than being a copy of xls-codec's own scheme:
216
219
 
217
220
  - **Location.** The `EncryptionHeader` sits unencrypted at the very start of the Table stream (`0Table`/`1Table`, whichever `FibBase.fWhichTblStm` selects), its own byte length given by `FibBase.lKey` — unlike `FilePass`, an inline record within the Workbook stream.
218
221
  - **Re-keying interval.** 512 bytes, not `xls-codec`'s 1024 (`archive-codec`'s `OFFICE_RC4_DOC_BLOCK_SIZE`) — a real [MS-DOC]-specific value, confirmed against Apache POI's `BinaryRC4Decryptor` independently of `xls-codec`'s own `Biff8DecryptingStream`.
219
222
  - **Per-stream block-zero origin.** WordDocument and Table are each encrypted independently, each with its own block-number counter starting at zero at that stream's own byte 0 — unlike `xls-codec`'s single continuous Workbook-stream offset. WordDocument's own unencrypted prefix is a fixed 68 bytes ([MS-DOC] 2.2.6.1/2.2.6.2's own stated literal); Table's is `FibBase.lKey`, since the `EncryptionHeader` occupying it has to be readable before any key can be derived at all. The Data stream is also encrypted in full per the spec, but this reader does not read the Data stream at all today, so decrypting it is out of scope until something needs to.
220
223
 
224
+ XOR obfuscation (Method 2) needs `archive-codec`'s own `createXorObfuscationKey`/`createXorObfuscationPasswordVerifier`/`createXorObfuscationArray`/`decryptXorObfuscationMethod2` (see [`archive-codec`'s own XOR obfuscation section](../archive-codec/README.md#xor-obfuscation-xlsdoc)) — genuinely simpler than RC4's own container layout, in one real way: `FibBase.lKey` carries the 32-bit password verifier itself here (its high 16 bits `createXorObfuscationKey`'s own output, its low 16 bits `createXorObfuscationPasswordVerifier`'s), not a Table-stream `EncryptionHeader` byte length, so the Table stream needs no unencrypted prefix at all and is obfuscated in full from its own byte 0 — confirmed against LibreOffice's own WW8 import (`ww8par.cxx`'s `DecryptXOR`, called with no prior seek for the Table stream, unlike WordDocument's own explicit 68-byte copy-then-decrypt). WordDocument's own 68-byte unencrypted prefix is unchanged from the RC4 case.
225
+
221
226
  `writeDocContent` never encrypts.
222
227
 
223
228
  ## Writing
224
229
 
225
- `writeDocContent` takes a `'wordprocessing'` `ContentDocument` with exactly one section, an optional `WriteDocContentOptions`, and produces real [MS-DOC] bytes wrapped in a real [MS-CFB] compound file, inverting every read-side structure listed above: a real piece table (`text/piece-table-write.ts`, always one uncompressed 16-bit piece — see [Why always uncompressed](#why-the-writer-always-writes-uncompressed-text)), `Sprm`-encoded grpprls for each run's and paragraph's own direct formatting (`prop/chp-write.ts`, `prop/pap-write.ts`), `ChpxFkp`/`PapxFkp` pages packed and split across as many 512-byte pages as the content needs (`prop/fkp-write.ts`), a real style sheet minting one entry per distinct `styleId`/`headingLevel` a document's paragraphs actually use (`style/stsh.ts`'s `buildStshForStyles`, [ExaDev/documents.js#1059](https://github.com/ExaDev/documents.js/issues/1059) — identity only, no formatting of a style's own; `FibRgFcLcb97.lcbStshf` "MUST be a nonzero value", so a document is never written without one, even one carrying zero styles, though this package's own reader tolerates a missing one), and a font table when at least one run names a font (`style/fonts.ts`). `WriteDocContentOptions.onWarning`, when given, is called for a non-fatal write-time degradation this writer chooses over throwing — today, only the lost-boundary fallback's own per-row budget check (see [Tables](#tables)'s own note on [ExaDev/documents.js#1013](https://github.com/ExaDev/documents.js/issues/1013)) — but that is not a guarantee the write itself goes on to succeed: a row whose own assigned lost boundaries cannot be trimmed down to a split that fits at all still reports one warning, describing its boundaries as unrecoverable and its fully-unsplit encoding as the fallback being attempted — and, should that unsplit encoding also overflow the row's own byte budget once `buildPapxPages` actually packs it, `writeDocContent` can still throw further down the same pipeline, after any rows still to come have had their own chance to report a warning: a caller can genuinely see this warning followed by a hard failure, but not necessarily right away, and not necessarily for the row whose warning it just read, since `buildPapxPages` packs paragraphs in document order and can fail first on an earlier row that also overflowed. Every genuine refusal this writer makes outright — the kinds named in the table below — still throws `DocFormatError`/`DocUnsupportedError` directly, with no warning first.
230
+ `writeDocContent` takes a `'wordprocessing'` `ContentDocument` with one or more sections, an optional `WriteDocContentOptions`, and produces real [MS-DOC] bytes wrapped in a real [MS-CFB] compound file, inverting every read-side structure listed above: a real piece table (`text/piece-table-write.ts`, always one uncompressed 16-bit piece — see [Why always uncompressed](#why-the-writer-always-writes-uncompressed-text)), `Sprm`-encoded grpprls for each run's and paragraph's own direct formatting (`prop/chp-write.ts`, `prop/pap-write.ts`), `ChpxFkp`/`PapxFkp` pages packed and split across as many 512-byte pages as the content needs (`prop/fkp-write.ts`), a real style sheet minting one entry per distinct `styleId`/`headingLevel` a document's paragraphs actually use (`style/stsh.ts`'s `buildStshForStyles`, [ExaDev/documents.js#1059](https://github.com/ExaDev/documents.js/issues/1059) — identity only, no formatting of a style's own; `FibRgFcLcb97.lcbStshf` "MUST be a nonzero value", so a document is never written without one, even one carrying zero styles, though this package's own reader tolerates a missing one), a font table when at least one run names a font (`style/fonts.ts`), a real `PlcfSed`/`Sepx` array with one entry per section (`prop/sep-write.ts`'s `buildPlcfSed`, `ExaDev/documents.js#971` — see [Sections](#sections)), and a `"Data"` stream carrying every inline picture's own real bytes when the document has at least one (`pictures-write.ts`'s `buildInlinePicture`, `data-stream.ts`'s `DataStreamBuilder`, `ExaDev/documents.js#971` — see [Images](#images)). `WriteDocContentOptions.onWarning`, when given, is called for a non-fatal write-time degradation this writer chooses over throwing — today, only the lost-boundary fallback's own per-row budget check (see [Tables](#tables)'s own note on [ExaDev/documents.js#1013](https://github.com/ExaDev/documents.js/issues/1013)) — but that is not a guarantee the write itself goes on to succeed: a row whose own assigned lost boundaries cannot be trimmed down to a split that fits at all still reports one warning, describing its boundaries as unrecoverable and its fully-unsplit encoding as the fallback being attempted — and, should that unsplit encoding also overflow the row's own byte budget once `buildPapxPages` actually packs it, `writeDocContent` can still throw further down the same pipeline, after any rows still to come have had their own chance to report a warning: a caller can genuinely see this warning followed by a hard failure, but not necessarily right away, and not necessarily for the row whose warning it just read, since `buildPapxPages` packs paragraphs in document order and can fail first on an earlier row that also overflowed. Every genuine refusal this writer makes outright — the kinds named in the table below — still throws `DocFormatError`/`DocUnsupportedError` directly, with no warning first.
226
231
 
227
- Character properties this writer converts, the exact inverse of [What is converted](#what-is-converted)'s character table above: `bold`, `italic`, `strike`, `underline` (as `kulSingle`, the only style a plain boolean can express), `sizePt`, `color` (via `sprmCCv`'s exact `COLORREF`, never the lossy 17-entry `sprmCIco` palette), and `fontFamily`. Paragraph properties: `alignment` (the four `ST_Jc`-aligned values this package's reader itself maps — `left`/`center`/`right`/`justify`), `indentLeftPt`, `indentRightPt`, `indentFirstLinePt`, `spacingBeforePt`, `spacingAfterPt`, `lineSpacing` (only `LSPD`'s multiplier form, matching the reader), `pageBreakBefore`, and `list` membership (`sprmPIlfo`/`sprmPIlvl` — see [Numbering definitions](#numbering-definitions) for the numbering tables this depends on).
232
+ Character properties this writer converts, the exact inverse of [What is converted](#what-is-converted)'s character table above: `bold`, `italic`, `strike`, `underline` (as `kulSingle`, the only style a plain boolean can express), `sizePt`, `color` (via `sprmCCv`'s exact `COLORREF`, never the lossy 17-entry `sprmCIco` palette), and `fontFamily`. Paragraph properties: `alignment` (the four `ST_Jc`-aligned values this package's reader itself maps — `left`/`center`/`right`/`justify`), `indentLeftPt`, `indentRightPt`, `indentFirstLinePt`, `spacingBeforePt`, `spacingAfterPt`, `lineSpacing` (only `LSPD`'s multiplier form, matching the reader), `pageBreakBefore`, `styleId`/`headingLevel` (via the minted STSH entry above), and `list` membership (`sprmPIlfo`/`sprmPIlvl` — see [Numbering definitions](#numbering-definitions) for the numbering tables this depends on).
228
233
 
229
234
  **Deliberately not handled**, beyond what the read-side scope table above already states applies to both directions:
230
235
 
231
- | Absent | Consequence |
232
- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
233
- | **Paragraph styles** | Every paragraph is written with `istd` 0 ("Normal"); `ContentParagraph.styleId` and `.headingLevel` are not written, and the style sheet this writer produces carries no styles for a future writer to target. |
234
- | **More than one section** | `writeDocContent` throws `DocUnsupportedError` for a `ContentDocument` with more than one `ContentSection`, rather than silently concatenating their blocks into what this package's own reader would read back as one anyway. |
235
- | **Non-paragraph blocks other than tables** | An image, page break, embedded object, or construct-boundary marker block throws `DocUnsupportedError` naming its own `kind` — `ContentTable` is now written (see [Tables](#tables)); a non-paragraph block found inside one of its own cells throws the identical error. |
236
- | **An empty `ContentDocument.sections[0].blocks`** | Written as a single paragraph with no runs — [MS-DOC] 2.4.2 requires the Main Document's own text to end in a paragraph mark, so an otherwise-empty section still needs one to hold it, exactly as a real producer's own blank document has one. |
236
+ | Absent | Consequence |
237
+ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
238
+ | **Non-paragraph, non-table, non-image blocks** | A `pageBreak`, `embeddedObject`, or construct-boundary marker (`constructStart`/`constructEnd`) block throws `DocUnsupportedError` naming its own `kind` — `ContentTable` and `ContentImageBlock` are now written (see [Tables](#tables) and [Images](#images)); a non-paragraph block found inside one of a table's own cells throws the identical error. A page break has no distinct [MS-DOC] representation this package's own reader has confirmed against a real producer (the closest candidate, a bare `0x000C` character embedded mid-run, is also the format's own end-of-section character — writing one there without a confirmed, independent read-side decoding would be exactly the kind of guess this package's own README states it never makes); an embedded object rides the same `PlcfSpa`/`OfficeArtDgContainer` drawing-tree mechanism this package's reader does not resolve at all (see the drawn-objects row above); a construct-boundary marker has no [MS-DOC] representation of its own to target until the cross-format `document-schema.js` design work tracked on [ExaDev/documents.js#1122](https://github.com/ExaDev/documents.js/issues/1122) lands. |
239
+ | **An empty `ContentSection.blocks`** | Written as a single paragraph with no runs — [MS-DOC] 2.4.2 requires the Main Document's own text to end in a paragraph mark, so an otherwise-empty section still needs one to hold it, exactly as a real producer's own blank document has one. |
237
240
 
238
241
  ### Why the writer always writes uncompressed text
239
242
 
@@ -260,6 +263,7 @@ The modules layer in the order [MS-DOC]'s own algorithms chain:
260
263
  | Module | What it does |
261
264
  | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
262
265
  | `src/bytes.ts` | Bounds-checked little-endian reads; every offset in the format is attacker-controlled data, so an over-read fails loudly. |
266
+ | `src/base64.ts` | Isomorphic base64 <-> `Uint8Array` conversion, shared by `pictures.ts` (encode) and `pictures-write.ts` (decode) for an inline picture's own raw file bytes. |
263
267
  | `src/plc.ts` | The `PLC` container shape, whose element count is derived from its total size by [MS-DOC] 2.2.2's own formula, and the "largest key at most" lookup every algorithm phrases in those words. |
264
268
  | `src/fib/` | The FIB's field offsets, derived by summing the declared field sizes, and the parse that reads the counts and offsets from them. |
265
269
  | `src/text/piece-table.ts` | The `Clx` and its `PlcPcd`, and the character-position-to-byte-offset mapping. |
@@ -283,7 +287,10 @@ The modules layer in the order [MS-DOC]'s own algorithms chain:
283
287
  | `src/list/numbering-write.ts` | The inverse of `list/numbering.ts`: a document's own paragraph list memberships to real `PlfLst`/`PlfLfo` bytes, and the numId-to-ilfo map `pap-write.ts` needs (see [Numbering definitions](#numbering-definitions)). |
284
288
  | `src/prop/fkp-write.ts` | Packs formatting exceptions into `ChpxFkp`/`PapxFkp` pages, splitting across as many as the content needs, and builds the bin tables addressing them. |
285
289
  | `src/table/tap-write.ts` | The inverse of `table/tap.ts`: a row's column boundaries, cell merge state, cell decoration and height to a `sprmTDefTable`/`sprmTDefTableShd`/`sprmTSetBrc`/`sprmTDyaRowHeight` grpprl. |
286
- | `src/table/write.ts` | Expanding a `ContentTable` into its own real physical-cell paragraph stream, for `write.ts`'s own paragraph pipeline to lay out like any other paragraph. |
290
+ | `src/table/write.ts` | Expanding a `ContentTable` into its own real physical-cell paragraph stream, and a `ContentImageBlock` into its own one-run picture-anchor paragraph, for `write.ts`'s own paragraph pipeline to lay out like any other paragraph. |
291
+ | `src/pictures-write.ts` | The inverse of `pictures.ts`: a `ContentImageBlock` to its own `PICF`/`OfficeArtInlineSpContainer`/blip bytes and `sprmCPicLocation` grpprl (see [Images](#images)). |
292
+ | `src/data-stream.ts` | Accumulates every inline picture's own bytes across a whole write into one real `"Data"` stream, handing each one back the offset it landed at. |
293
+ | `src/prop/sep-write.ts` | The inverse of `prop/sep.ts`: a document's own sections to a real `PlcfSed`/`Sepx` array, one entry per section (see [Sections](#sections)). |
287
294
  | `src/write.ts` | The whole write chain, from a `ContentDocument` to real [MS-DOC] bytes in a real [MS-CFB] compound file. |
288
295
 
289
296
  ### Why the piece table gets the most attention
@@ -324,7 +331,7 @@ const written = writeDocContent({
324
331
  });
325
332
  ```
326
333
 
327
- `readDocContent` throws a `DocFormatError` when the bytes do not conform to [MS-DOC], and a `DocUnsupportedError` when they conform but use a feature this package deliberately refuses rather than approximates (XOR obfuscation or RC4 CryptoAPI encryption, an RC4-encrypted document given no password or the wrong one, or the `sprmPChgTabs` sentinel above). `writeDocContent` throws a `DocUnsupportedError` for a document, section count, or block kind outside its own scope (see [Writing](#writing)) and a `DocFormatError` for a value that would need a property out of a sprm's own operand range (a font size or indent too large to fit its 2-byte operand, for instance).
334
+ `readDocContent` throws a `DocFormatError` when the bytes do not conform to [MS-DOC], and a `DocUnsupportedError` when they conform but use a feature this package deliberately refuses rather than approximates (RC4 CryptoAPI encryption, an encrypted or obfuscated document given no password or the wrong one, or the `sprmPChgTabs` sentinel above). `writeDocContent` throws a `DocUnsupportedError` for a document, section count, or block kind outside its own scope (see [Writing](#writing)) and a `DocFormatError` for a value that would need a property out of a sprm's own operand range (a font size or indent too large to fit its 2-byte operand, for instance).
328
335
 
329
336
  ## Worker-isomorphic
330
337
 
@@ -336,6 +343,8 @@ Every structure is tested against bytes hand-assembled from [MS-DOC]'s own field
336
343
 
337
344
  The writer is verified the opposite way: `src/write.test.ts` reads every document `writeDocContent` produces back through this package's own `readDocContent`, including cases that force `ChpxFkp`/`PapxFkp` page-splitting (150 distinctly-formatted runs, 60 distinctly-indented paragraphs) rather than relying only on the common one-page case, a dedicated `describe("writeDocContent numbering")` block covering a multi-level list's own `format`/`text` per level, a bulleted list's glyph, separate-`ilfo` minting for separate `numId`s, a no-lists document writing no numbering tables at all, and a list membership inside a table cell (see [Numbering definitions](#numbering-definitions)), and a dedicated `describe("writeDocContent tables")` block covering row/column/cell round-tripping, a multi-paragraph cell, row height, a horizontally merged cell's `colSpan`, a vertically merged cell's `rowSpan`, the lost-boundary fallback recovering `colSpan`/`columnWidthsPt` for a single-row merge and for a multi-row table that merges the identical boundary in every row ([ExaDev/documents.js#992](https://github.com/ExaDev/documents.js/issues/992)), that same fallback's own per-row budget check degrading gracefully via `onWarning` instead of throwing once a row's assigned split would overflow its row-ending mark -- a two-row table sitting exactly at the 21-physical-cell ceiling versus one column past it (only the over-budget row trims, so the other row's `#992` recovery survives, and the trimmed row itself still recovers all but one of its own assigned boundaries rather than losing all of them), a single-row table one column past that same ceiling recovering all but one of its own boundaries the identical way with no sibling row to share the work with, and a single-row table whose full assigned split would need 64 physical cells -- one past `TDefTableOperand`'s own hard `NumberOfColumns` ceiling ([MS-DOC] 2.9.321's own "MUST NOT exceed 63", not 2.4.3's separate "between 1 and 63 table cells" limit) -- trimming down to the row-ending mark's own byte-budget ceiling instead of throwing the way this writer used to before that check ran ahead of `encodeTableRowGrpprl`'s own unconditional throw past it ([ExaDev/documents.js#1013](https://github.com/ExaDev/documents.js/issues/1013)), the nested-table refusal, and a `describe("cell decoration")` block round-tripping a solid background fill, a genuine two-colour pattern fill (a percentage grey, a crosshatch -- ExaDev/documents.js#951), all four borders at different styles/widths/colours, a partially bordered cell, a cell with no decoration at all (which must emit none), decoration on a merged cell, and a colour the `Ico` palette cannot hold. `src/table/decoration.test.ts` covers the same vocabulary one layer down, against bytes hand-built from the specification's own field tables -- including every encoding this package's own writer never emits, which a round trip therefore cannot reach: both no-border spellings, each `BrcType` family's collapse onto `ContentStrokeStyle`, the art-border and automatic-colour cases, `ipatSolid`, every pattern `IPAT_TO_PATTERN_TYPE` names (percentages and stripe/cross families alike) alongside `ipatNil` and an `ipatPctNew*` value with no `ST_Shd` equivalent, the `DocUnsupportedError` a SpreadsheetML-only pattern name throws on write, `Shd80`, and the second and third shading arrays a row too wide for one `PapxInFkp` record can never exercise end to end. Beyond the committed suite, a `writeDocContent` sample carrying every character and paragraph property this writer supports was opened, rendered, and re-exported by a real, independent [MS-DOC] implementation — LibreOffice — without error or visible content loss, confirming those bytes are genuinely conformant to a reader this package did not write, not merely self-consistent with its own. Table samples were checked the same way and now pass in both directions -- plain, vertically merged, horizontally merged, merged both ways, and decorated with cell fills and per-side borders -- after [ExaDev/documents.js#892](https://github.com/ExaDev/documents.js/issues/892) and [#895](https://github.com/ExaDev/documents.js/issues/895) were each root-caused by comparing this writer's own bytes against a genuine LibreOffice-authored `.doc`; see [Tables](#tables) for the full findings.
338
345
 
346
+ A `describe("writeDocContent multiple sections")` block (`ExaDev/documents.js#971`) covers two and three sections each round-tripping their own distinct page size and margins independently, and a non-final section whose own last block is a table still closing on a genuine paragraph mark before its end-of-section character, rather than landing on the table's own row-ending mark. A `describe("writeDocContent inline pictures")` block covers a PNG and a JPEG image round-tripping their own raw bytes/format/size, a paragraph's own text splitting around an inline picture into separate blocks, more than one picture landing at distinct offsets in the same `"Data"` stream, an unwritable format (`svg`) throwing `DocUnsupportedError`, and no `"Data"` stream at all being written for a picture-free document. Neither of these two write-side additions has yet been checked against a real, independent [MS-DOC] implementation the way [Tables](#tables) and [Numbering definitions](#numbering-definitions) have -- that is the next thing worth doing here, mirroring how each of those was itself first verified by this package's own round trip alone.
347
+
339
348
  There is no real-world conformance corpus on the read side, and the write side inherits the same gap for the same reason: the tests prove this package matches the published specification, which is not the same as proving it matches what Word itself reads or writes between 1997 and 2007. Anyone extending this package should treat a corpus as the next thing worth building.
340
349
 
341
350
  ## Specification
@@ -0,0 +1,48 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/base64.ts
3
+ const TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4
+ const DECODE = (() => {
5
+ const map = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
6
+ for (let index = 0; index < 64; index += 1) map[TABLE.charCodeAt(index)] = index;
7
+ return map;
8
+ })();
9
+ function bytesToBase64(bytes) {
10
+ let out = "";
11
+ const len = bytes.length;
12
+ for (let index = 0; index < len; index += 3) {
13
+ const b0 = bytes[index] ?? 0;
14
+ const b1 = index + 1 < len ? bytes[index + 1] ?? 0 : 0;
15
+ const b2 = index + 2 < len ? bytes[index + 2] ?? 0 : 0;
16
+ out += TABLE.charAt(b0 >> 2);
17
+ out += TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
18
+ out += index + 1 < len ? TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
19
+ out += index + 2 < len ? TABLE.charAt(b2 & 63) : "=";
20
+ }
21
+ return out;
22
+ }
23
+ function base64ToBytes(base64) {
24
+ const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
25
+ const len = clean.length;
26
+ const out = new Uint8Array(len * 3 / 4 | 0);
27
+ let position = 0;
28
+ for (let index = 0; index < len; index += 4) {
29
+ const c0 = DECODE[clean.charCodeAt(index)] ?? 255;
30
+ const c1 = DECODE[clean.charCodeAt(index + 1)] ?? 255;
31
+ const c2 = clean.charCodeAt(index + 2);
32
+ const c3 = clean.charCodeAt(index + 3);
33
+ if (c0 === 255 || c1 === 255) throw new Error("invalid base64 input");
34
+ out[position++] = c0 << 2 | c1 >> 4;
35
+ if (c2 !== 61) {
36
+ const d2 = DECODE[c2] ?? 255;
37
+ out[position++] = (c1 & 15) << 4 | d2 >> 2;
38
+ if (c3 !== 61) {
39
+ const d3 = DECODE[c3] ?? 255;
40
+ out[position++] = (d2 & 3) << 6 | d3;
41
+ }
42
+ }
43
+ }
44
+ return out.subarray(0, position);
45
+ }
46
+ //#endregion
47
+ exports.base64ToBytes = base64ToBytes;
48
+ exports.bytesToBase64 = bytesToBase64;
@@ -0,0 +1,5 @@
1
+ //#region src/base64.d.ts
2
+ declare function bytesToBase64(bytes: Uint8Array): string;
3
+ declare function base64ToBytes(base64: string): Uint8Array<ArrayBuffer>;
4
+ //#endregion
5
+ export { base64ToBytes, bytesToBase64 };
@@ -0,0 +1,5 @@
1
+ //#region src/base64.d.ts
2
+ declare function bytesToBase64(bytes: Uint8Array): string;
3
+ declare function base64ToBytes(base64: string): Uint8Array<ArrayBuffer>;
4
+ //#endregion
5
+ export { base64ToBytes, bytesToBase64 };
package/dist/base64.js ADDED
@@ -0,0 +1,46 @@
1
+ //#region src/base64.ts
2
+ const TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
+ const DECODE = (() => {
4
+ const map = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
5
+ for (let index = 0; index < 64; index += 1) map[TABLE.charCodeAt(index)] = index;
6
+ return map;
7
+ })();
8
+ function bytesToBase64(bytes) {
9
+ let out = "";
10
+ const len = bytes.length;
11
+ for (let index = 0; index < len; index += 3) {
12
+ const b0 = bytes[index] ?? 0;
13
+ const b1 = index + 1 < len ? bytes[index + 1] ?? 0 : 0;
14
+ const b2 = index + 2 < len ? bytes[index + 2] ?? 0 : 0;
15
+ out += TABLE.charAt(b0 >> 2);
16
+ out += TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
17
+ out += index + 1 < len ? TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
18
+ out += index + 2 < len ? TABLE.charAt(b2 & 63) : "=";
19
+ }
20
+ return out;
21
+ }
22
+ function base64ToBytes(base64) {
23
+ const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
24
+ const len = clean.length;
25
+ const out = new Uint8Array(len * 3 / 4 | 0);
26
+ let position = 0;
27
+ for (let index = 0; index < len; index += 4) {
28
+ const c0 = DECODE[clean.charCodeAt(index)] ?? 255;
29
+ const c1 = DECODE[clean.charCodeAt(index + 1)] ?? 255;
30
+ const c2 = clean.charCodeAt(index + 2);
31
+ const c3 = clean.charCodeAt(index + 3);
32
+ if (c0 === 255 || c1 === 255) throw new Error("invalid base64 input");
33
+ out[position++] = c0 << 2 | c1 >> 4;
34
+ if (c2 !== 61) {
35
+ const d2 = DECODE[c2] ?? 255;
36
+ out[position++] = (c1 & 15) << 4 | d2 >> 2;
37
+ if (c3 !== 61) {
38
+ const d3 = DECODE[c3] ?? 255;
39
+ out[position++] = (d2 & 3) << 6 | d3;
40
+ }
41
+ }
42
+ }
43
+ return out.subarray(0, position);
44
+ }
45
+ //#endregion
46
+ export { base64ToBytes, bytesToBase64 };
@@ -0,0 +1,11 @@
1
+ //#region src/data-stream.d.ts
2
+ declare class DataStreamBuilder {
3
+ private readonly chunks;
4
+ private length;
5
+ /** Appends `bytes` and returns the offset they now occupy in the eventual stream. */
6
+ append(bytes: Uint8Array): number;
7
+ /** The whole accumulated stream, empty when nothing was ever appended -- write.ts only adds a real "Data" stream to the compound file when this is non-empty, matching what a valid Word Binary File with no pictures needs (pictures.ts's own reader treats an absent Data stream as "no pictures", not malformed input). */
8
+ build(): Uint8Array<ArrayBuffer>;
9
+ }
10
+ //#endregion
11
+ export { DataStreamBuilder as t };
@@ -0,0 +1,11 @@
1
+ //#region src/data-stream.d.ts
2
+ declare class DataStreamBuilder {
3
+ private readonly chunks;
4
+ private length;
5
+ /** Appends `bytes` and returns the offset they now occupy in the eventual stream. */
6
+ append(bytes: Uint8Array): number;
7
+ /** The whole accumulated stream, empty when nothing was ever appended -- write.ts only adds a real "Data" stream to the compound file when this is non-empty, matching what a valid Word Binary File with no pictures needs (pictures.ts's own reader treats an absent Data stream as "no pictures", not malformed input). */
8
+ build(): Uint8Array<ArrayBuffer>;
9
+ }
10
+ //#endregion
11
+ export { DataStreamBuilder as t };
@@ -0,0 +1,25 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/data-stream.ts
3
+ var DataStreamBuilder = class {
4
+ chunks = [];
5
+ length = 0;
6
+ /** Appends `bytes` and returns the offset they now occupy in the eventual stream. */
7
+ append(bytes) {
8
+ const offset = this.length;
9
+ this.chunks.push(bytes);
10
+ this.length += bytes.length;
11
+ return offset;
12
+ }
13
+ /** The whole accumulated stream, empty when nothing was ever appended -- write.ts only adds a real "Data" stream to the compound file when this is non-empty, matching what a valid Word Binary File with no pictures needs (pictures.ts's own reader treats an absent Data stream as "no pictures", not malformed input). */
14
+ build() {
15
+ const out = new Uint8Array(this.length);
16
+ let cursor = 0;
17
+ for (const chunk of this.chunks) {
18
+ out.set(chunk, cursor);
19
+ cursor += chunk.length;
20
+ }
21
+ return out;
22
+ }
23
+ };
24
+ //#endregion
25
+ exports.DataStreamBuilder = DataStreamBuilder;
@@ -0,0 +1,2 @@
1
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.cjs";
2
+ export { DataStreamBuilder };
@@ -0,0 +1,2 @@
1
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.js";
2
+ export { DataStreamBuilder };
@@ -0,0 +1,24 @@
1
+ //#region src/data-stream.ts
2
+ var DataStreamBuilder = class {
3
+ chunks = [];
4
+ length = 0;
5
+ /** Appends `bytes` and returns the offset they now occupy in the eventual stream. */
6
+ append(bytes) {
7
+ const offset = this.length;
8
+ this.chunks.push(bytes);
9
+ this.length += bytes.length;
10
+ return offset;
11
+ }
12
+ /** The whole accumulated stream, empty when nothing was ever appended -- write.ts only adds a real "Data" stream to the compound file when this is non-empty, matching what a valid Word Binary File with no pictures needs (pictures.ts's own reader treats an absent Data stream as "no pictures", not malformed input). */
13
+ build() {
14
+ const out = new Uint8Array(this.length);
15
+ let cursor = 0;
16
+ for (const chunk of this.chunks) {
17
+ out.set(chunk, cursor);
18
+ cursor += chunk.length;
19
+ }
20
+ return out;
21
+ }
22
+ };
23
+ //#endregion
24
+ export { DataStreamBuilder };
@@ -44,27 +44,61 @@ function verifyPassword(baseHash, header) {
44
44
  if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new require_errors.DocUnsupportedError("incorrect password for RC4-encrypted document");
45
45
  }
46
46
  /** Decrypts everything after `prefixLength` bytes of `stream`, leaving the prefix itself untouched -- WORD_DOCUMENT_UNENCRYPTED_PREFIX for WordDocument, FibBase.lKey for Table, each stream's own block-number counter starting fresh at its own byte 0 (this file's own top comment, point 3). */
47
- function decryptStream(baseHash, stream, prefixLength) {
47
+ function decryptStreamRc4(baseHash, stream, prefixLength) {
48
48
  const decrypted = new Uint8Array(stream.length);
49
49
  decrypted.set(stream.subarray(0, prefixLength), 0);
50
50
  decrypted.set((0, archive_codec.decryptOfficeRc4)(baseHash, prefixLength, stream.subarray(prefixLength), archive_codec.OFFICE_RC4_DOC_BLOCK_SIZE), prefixLength);
51
51
  return decrypted;
52
52
  }
53
- /**
54
- * Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
55
- *
56
- * Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
57
- */
58
- function decryptDocStreams(wordDocument, table, password) {
59
- if (password === void 0) throw new require_errors.DocUnsupportedError("this document is RC4-encrypted ([MS-DOC] 2.2.6.2); call readDocContent with a password to decrypt it");
53
+ /** Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader. */
54
+ function decryptDocStreamsRc4(wordDocument, table, password) {
60
55
  const header = readRc4Header(table);
61
56
  const baseHash = (0, archive_codec.deriveOfficeRc4BaseHash)(password, header.salt);
62
57
  verifyPassword(baseHash, header);
63
58
  const lKey = require_bytes.readUint32LE(wordDocument, 14);
64
59
  return {
65
- wordDocument: decryptStream(baseHash, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
66
- table: decryptStream(baseHash, table, lKey)
60
+ wordDocument: decryptStreamRc4(baseHash, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
61
+ table: decryptStreamRc4(baseHash, table, lKey)
67
62
  };
68
63
  }
64
+ /** Decrypts everything after `prefixLength` bytes of `stream` against Method 2's own transform, leaving the prefix itself untouched -- `initialIndex` is `prefixLength % 16`, the XorArrayIndex the decrypted span's own first byte starts at (confirmed against LibreOffice's own `ww8par.cxx` `DecryptXOR`, whose `InitCipher(); Skip(nSt)` is exactly this: reset to 0, then advance by the skipped prefix's own length mod 16). */
65
+ function decryptStreamXor(array, stream, prefixLength) {
66
+ const decrypted = new Uint8Array(stream.length);
67
+ decrypted.set(stream.subarray(0, prefixLength), 0);
68
+ decrypted.set((0, archive_codec.decryptXorObfuscationMethod2)(array, stream.subarray(prefixLength), prefixLength % archive_codec.XOR_OBFUSCATION_ARRAY_LENGTH), prefixLength);
69
+ return decrypted;
70
+ }
71
+ /**
72
+ * Decrypts an XOR-obfuscated (fEncrypted=1, fObfuscated=1) document's WordDocument and Table streams given the password, verifying it first against FibBase's own lKey field -- not a Table-stream EncryptionHeader the way RC4 needs, see this file's own top comment for why. The Table stream carries no unencrypted prefix under this scheme, unlike RC4's own FibBase.lKey-byte EncryptionHeader; Data (also obfuscated per [MS-DOC], from its own byte 0) is out of scope, matching decryptDocStreamsRc4 and this package's own read.ts, which does not read the Data stream at all.
73
+ */
74
+ function decryptDocStreamsXor(wordDocument, table, password) {
75
+ const lKey = require_bytes.readUint32LE(wordDocument, 14);
76
+ const headerKey = lKey >>> 16 & 65535;
77
+ const headerVerifier = lKey & 65535;
78
+ let computedKey;
79
+ let computedVerifier;
80
+ try {
81
+ computedKey = (0, archive_codec.createXorObfuscationKey)(password);
82
+ computedVerifier = (0, archive_codec.createXorObfuscationPasswordVerifier)(password);
83
+ } catch (error) {
84
+ if (error instanceof RangeError) throw new require_errors.DocUnsupportedError("incorrect password for XOR-obfuscated document");
85
+ throw error;
86
+ }
87
+ if (computedKey !== headerKey || computedVerifier !== headerVerifier) throw new require_errors.DocUnsupportedError("incorrect password for XOR-obfuscated document");
88
+ const array = (0, archive_codec.createXorObfuscationArray)(password, archive_codec.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2);
89
+ return {
90
+ wordDocument: decryptStreamXor(array, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
91
+ table: decryptStreamXor(array, table, 0)
92
+ };
93
+ }
94
+ /**
95
+ * Decrypts an encrypted document's WordDocument and Table streams given the password, dispatching on `fObfuscated` between [MS-DOC] 2.2.6.2's RC4 encryption header and 2.2.6.1's XOR obfuscation (Method 2).
96
+ *
97
+ * Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
98
+ */
99
+ function decryptDocStreams(wordDocument, table, password, fObfuscated) {
100
+ if (password === void 0) throw new require_errors.DocUnsupportedError(`this document is ${fObfuscated ? "XOR-obfuscated ([MS-DOC] 2.2.6.1)" : "RC4-encrypted ([MS-DOC] 2.2.6.2)"}; call readDocContent with a password to decrypt it`);
101
+ return fObfuscated ? decryptDocStreamsXor(wordDocument, table, password) : decryptDocStreamsRc4(wordDocument, table, password);
102
+ }
69
103
  //#endregion
70
104
  exports.decryptDocStreams = decryptDocStreams;
@@ -4,10 +4,10 @@ interface DecryptedDocStreams {
4
4
  readonly table: Uint8Array<ArrayBuffer>;
5
5
  }
6
6
  /**
7
- * Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
7
+ * Decrypts an encrypted document's WordDocument and Table streams given the password, dispatching on `fObfuscated` between [MS-DOC] 2.2.6.2's RC4 encryption header and 2.2.6.1's XOR obfuscation (Method 2).
8
8
  *
9
9
  * Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
10
10
  */
11
- declare function decryptDocStreams(wordDocument: Uint8Array<ArrayBuffer>, table: Uint8Array<ArrayBuffer>, password: string | undefined): DecryptedDocStreams;
11
+ declare function decryptDocStreams(wordDocument: Uint8Array<ArrayBuffer>, table: Uint8Array<ArrayBuffer>, password: string | undefined, fObfuscated: boolean): DecryptedDocStreams;
12
12
  //#endregion
13
13
  export { DecryptedDocStreams, decryptDocStreams };