doc-codec 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +16 -10
  2. package/dist/fib/write.cjs +2 -0
  3. package/dist/fib/write.d.cts +5 -0
  4. package/dist/fib/write.d.ts +5 -0
  5. package/dist/fib/write.js +2 -0
  6. package/dist/index.cjs +1 -0
  7. package/dist/index.d.cts +4 -4
  8. package/dist/index.d.ts +4 -4
  9. package/dist/index.js +2 -2
  10. package/dist/list/numbering-write.cjs +165 -0
  11. package/dist/list/numbering-write.d.cts +21 -0
  12. package/dist/list/numbering-write.d.ts +21 -0
  13. package/dist/list/numbering-write.js +163 -0
  14. package/dist/list/numbering.cjs +11 -10
  15. package/dist/list/numbering.d.cts +2 -2
  16. package/dist/list/numbering.d.ts +2 -2
  17. package/dist/list/numbering.js +11 -11
  18. package/dist/{numbering-Cir-uGdc.d.cts → numbering-Bivayss4.d.cts} +5 -3
  19. package/dist/{numbering-DF1z1PyG.d.ts → numbering-Co4jssbG.d.ts} +5 -3
  20. package/dist/{pap-BmelEYEP.d.cts → pap-BT1p0uzu.d.cts} +1 -1
  21. package/dist/{pap-BkFo8dZO.d.ts → pap-DOFLMWcG.d.ts} +1 -1
  22. package/dist/prop/chp-write.cjs +1 -1
  23. package/dist/prop/chp-write.js +1 -1
  24. package/dist/prop/chp.d.cts +1 -1
  25. package/dist/prop/chp.d.ts +1 -1
  26. package/dist/prop/pap-write.cjs +15 -2
  27. package/dist/prop/pap-write.d.cts +1 -1
  28. package/dist/prop/pap-write.d.ts +1 -1
  29. package/dist/prop/pap-write.js +15 -2
  30. package/dist/prop/pap.d.cts +1 -1
  31. package/dist/prop/pap.d.ts +1 -1
  32. package/dist/prop/sep.d.cts +1 -1
  33. package/dist/prop/sep.d.ts +1 -1
  34. package/dist/prop/sprm.cjs +1 -1
  35. package/dist/prop/sprm.d.cts +1 -1
  36. package/dist/prop/sprm.d.ts +1 -1
  37. package/dist/prop/sprm.js +1 -1
  38. package/dist/read.cjs +1 -0
  39. package/dist/read.d.cts +3 -3
  40. package/dist/read.d.ts +3 -3
  41. package/dist/read.js +1 -0
  42. package/dist/{sprm-Djg5RNiq.d.cts → sprm-DEyvdOo0.d.cts} +1 -1
  43. package/dist/{sprm-Djg5RNiq.d.ts → sprm-DEyvdOo0.d.ts} +1 -1
  44. package/dist/table/tap.d.cts +1 -1
  45. package/dist/table/tap.d.ts +1 -1
  46. package/dist/table/write.d.cts +1 -1
  47. package/dist/table/write.d.ts +1 -1
  48. package/dist/{write-C_vJizAM.d.cts → write-C9XbykDf.d.cts} +1 -1
  49. package/dist/{write-C_vJizAM.d.ts → write-C9XbykDf.d.ts} +1 -1
  50. package/dist/write.cjs +20 -2
  51. package/dist/write.d.cts +1 -1
  52. package/dist/write.d.ts +1 -1
  53. package/dist/write.js +20 -2
  54. package/package.json +3 -3
package/README.md CHANGED
@@ -22,12 +22,12 @@ 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
- - **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. Read-only; see [Numbering definitions](#numbering-definitions).
25
+ - **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.
26
26
  - **The one section's own page size and margins** — `prop/sep.ts`'s `readSectionProperties` resolves `PlcfSed`/`Sepx` (`sprmSXaPage`/`sprmSYaPage`/`sprmSDxaLeft`/`sprmSDxaRight`/`sprmSDyaTop`/`sprmSDyaBottom`) into `ContentSection.pageSize`/`.margins`, 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 it leaves unstated. This reader only ever resolves the section spanning the whole main document — the "Section boundaries" row below states what "one section" still means.
27
27
 
28
28
  Built and shipped, on the write side — see [Writing](#writing) for the full scope statement:
29
29
 
30
- - **`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, 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)).
30
+ - **`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)).
31
31
  - 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.
32
32
 
33
33
  **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:
@@ -43,7 +43,6 @@ Built and shipped, on the write side — see [Writing](#writing) for the full sc
43
43
  | **Encryption** | An encrypted or XOR-obfuscated document is refused with a `DocUnsupportedError` rather than read as plaintext. `writeDocContent` never encrypts. |
44
44
  | **`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. |
45
45
  | **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. |
46
- | **Right-margin paragraph indent** | `pap.ts`'s reader folds `sprmPDxaRight` into an internal `indentRightPt`, but `ContentParagraphSchema` (`document-schema.js`) carries no field for it, so no reader output and no writer input can ever carry it. |
47
46
  | **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. |
48
47
 
49
48
  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.
@@ -70,6 +69,7 @@ Paragraph properties, from `PapxInFkp` grpprls:
70
69
  | `sprmPJc` (0x2461), `sprmPJc80` (0x2403) | `alignment` |
71
70
  | `sprmPDxaLeft` (0x845E) / `sprmPDxaLeft80` (0x840F) | `indentLeftPt` |
72
71
  | `sprmPDxaLeft1` (0x8460) / `sprmPDxaLeft180` (0x8411) | `indentFirstLinePt` |
72
+ | `sprmPDxaRight` (0x845D) / `sprmPDxaRight80` (0x840E) | `indentRightPt` |
73
73
  | `sprmPDyaBefore` (0xA413), `sprmPDyaAfter` (0xA414) | `spacingBeforePt` / `spacingAfterPt` |
74
74
  | `sprmPDyaLine` (0x6412) | `lineSpacing`, only for `LSPD`'s multiplier form |
75
75
  | `sprmPFPageBreakBefore` (0x2407) | `pageBreakBefore` |
@@ -142,16 +142,20 @@ A paragraph's own `list.numId`/`list.level` (`sprmPIlfo`/`sprmPIlvl`, unchanged
142
142
 
143
143
  **Deliberately shaped like ooxml.js's own numbering, not document-schema.js's.** `NumberingDefinition`/`NumberingLevel` are doc-codec's own types, not a `document-schema.js` addition: `ContentListMembership` is shared verbatim across every codec in this family, and widening it with a doc-codec-specific numbering-definition payload would leak this package's own model into a schema the sibling packages also depend on -- exactly the reasoning `ooxml.js`'s own `typed/docx/numbering.ts` states for `word/numbering.xml`'s `abstractNum`/`num` tables, which this module deliberately mirrors rather than reinvents. `NumberingLevel.format` is the identical ECMA-376 `ST_NumberFormat` string ooxml.js's own field already carries (`"decimal"`, `"upperRoman"`, `"bullet"`, ...) -- [MS-OSHARED] 2.2.1.3's own `MSONFC` enumeration documents each value as "mapped to the `ST_NumberFormat`... equivalent", so this reader uses that same mapping rather than inventing a second vocabulary. `NumberingLevel.text` is the identical `'%1.'`/`'%2)'`-style placeholder convention: `[MS-DOC]`'s own `Xst`/`rgbxchNums` encoding names a placeholder by which _character position_ in the level's text is a raw, zero-based level index rather than literal content, and `readLevelText` converts that into the one-based `%N` spelling ooxml.js's own `w:lvlText` values already use -- so a consumer that already resolves one already resolves the other.
144
144
 
145
- **Read-only, matching ooxml.js's own docx writer exactly.** `word/numbering.xml` is read into `DocxDocument.numbering` but never written back (that package's own stated write scope), and `writeDocContent` does not attempt to write `PlfLst`/`PlfLfo` either: encoding a level's own `grpprlPapx`/`grpprlChpx` `Prl` streams back out is a materially separate task, the identical reasoning [Writer scope](#writing) states for why `xls-codec`'s formula writing is scoped apart from its read-side recovery.
145
+ **Written too, as of this package's own inverse -- `ooxml.js`'s own docx writer is unaffected.** `list/numbering-write.ts` is `list/numbering.ts`'s inverse: `gatherListUsage` walks the document's own flattened paragraph sequence (table cells included, so a list used only inside a cell still resolves) and reconstructs a real `NumberingDefinitions` from each paragraph's `list.numId`/`.level`/`.format` -- `ContentListMembership` carries no full level table of its own, only one paragraph's own membership, so the writer mints one from what the document's paragraphs actually use, assigning a fresh one-based `ilfo` to each distinct `numId` in first-occurrence order (exactly the value `numbering.ts`'s own reader would assign it back on a re-read, since that reader's `numId` IS the `ilfo`, stringified). `buildNumberingTables` then encodes that `NumberingDefinitions` into real `PlfLst`/`PlfLfo` bytes, and `write.ts` places both in the Table stream and populates `FibRgFcLcb97.fcPlfLst`/`lcbPlfLst`/`fcPlfLfo`/`lcbPlfLfo` -- left at 0/0, matching what `readNumberingDefinitions` itself treats as "no lists", for a document with no `list` membership at all. `buildNumberingTables` is independent of `gatherListUsage` -- a hand-built `NumberingDefinitions` round-trips through it too, since every `LVLF` field it writes comes from the definition's own `NumberingLevel` rather than an assumed default -- and validates accordingly: a definition keyed by a string that collides with another key once both are converted to a number (`"1"` and `"01"` both become the lsid `1`, even though `Object.keys` never canonicalises `"01"` the way it does a true integer-index key) throws a `DocFormatError` naming both colliding keys, since [MS-DOC] 2.9.147 states `lsid` "MUST be unique for each LSTF" and `readNumberingDefinitions` resolves an `LFO` to its `LSTF` purely by matching `lsid` -- a silent collision would make the colliding lists indistinguishable on the next read. `gatherListUsage`'s own minted keys (`ilfoByNumId.size + 1`, stringified) can never collide this way, so the check is reachable only through a hand-built `NumberingDefinitions`, the same route every other `buildNumberingTables` refusal below is reachable through. `pap-write.ts`'s `encodeParagraphGrpprl` takes the resulting numId-to-ilfo map as its own `ilfoOf` parameter to write each paragraph's `sprmPIlfo`/`sprmPIlvl`. A round trip through this package alone is stable, but a `numId` minted by a _different_ producer or codec is not preserved verbatim -- [MS-DOC] addresses a list by its one-based `ilfo`, not an opaque identifier, so it is renumbered to whichever `ilfo` this document's own write happens to mint it. `word/numbering.xml` is a separate package's separate decision: it is read into `DocxDocument.numbering` but never written back by `ooxml.js`'s own docx writer (that package's own stated write scope), and nothing here changes that.
146
146
 
147
- **What is deliberately not resolved**, each a genuine layer of the format rather than an oversight:
147
+ **What this writer does NOT write, matching this reader's own gaps below exactly -- there was never anything decoded here for a writer to round-trip.** A level's own `grpprlPapx`/`grpprlChpx` `Prl` streams are always written empty (`cbGrpprlChpx`/`cbGrpprlPapx` both 0): a real, valid, minimal `LVL`, just one carrying no per-level direct formatting a real Word list might otherwise have, since `NumberingLevel` has no field for either. `LFOLVL` overrides (`PlfLfo`'s own `rgLfoData`) are never written either, matching the reader never applying them.
148
148
 
149
- - **`LFOLVL` overrides.** An `LFO` can restate one or more of its `LSTF`'s own levels with different formatting (`PlfLfo`'s own `rgLfoData`); this reader always resolves an `ilfo` straight through to its `LSTF`'s own plain `LVL` array, ignoring any override the `LFO` itself carries. `PlfLfo`'s own `rgLfo` (fixed 16-byte records) is all this reader touches; `rgLfoData`, which sits immediately after it, is never read at all.
150
- - **`grpprlPapx`/`grpprlChpx`.** A level's own paragraph/character formatting `Prl` streams are skipped past by their declared length, never decoded, since `ContentListMembership` has nowhere to carry per-level indent or font direct formatting.
151
- - **Legal numbering (`LVLF.fLegal`).** A bit that overrides an _inherited_ placeholder's own format (forcing it to `msonfcArabic`, or preserving `msonfcArabicLZ`) rather than the level's own -- `text` still carries the placeholder verbatim, uninterpreted by `fLegal`.
149
+ **What is deliberately not resolved, in either direction**, each a genuine layer of the format rather than an oversight:
150
+
151
+ - **`LFOLVL` overrides.** An `LFO` can restate one or more of its `LSTF`'s own levels with different formatting (`PlfLfo`'s own `rgLfoData`); this reader always resolves an `ilfo` straight through to its `LSTF`'s own plain `LVL` array, ignoring any override the `LFO` itself carries. `PlfLfo`'s own `rgLfo` (fixed 16-byte records) is all this reader touches; `rgLfoData`, which sits immediately after it, is never read or written at all.
152
+ - **`grpprlPapx`/`grpprlChpx`.** A level's own paragraph/character formatting `Prl` streams are skipped past by their declared length on read, never decoded, since `ContentListMembership` has nowhere to carry per-level indent or font direct formatting -- and, for the identical reason, always written empty (see above).
153
+ - **Legal numbering (`LVLF.fLegal`).** A bit that overrides an _inherited_ placeholder's own format (forcing it to `msonfcArabic`, or preserving `msonfcArabicLZ`) rather than the level's own -- `text` still carries the placeholder verbatim, uninterpreted by `fLegal`, and this writer never sets the bit.
152
154
 
153
155
  **Verified against a real, independent [MS-DOC] implementation, not just this package's own hand-built fixtures.** A `.doc` built directly by LibreOffice (`soffice --headless --convert-to doc`, from a hand-authored `.fodt` declaring a real `text:list-style` numbered list and a separate bulleted list) is read correctly by this reader: the numbered list's own level 0 resolves to `format: "decimal"`, `text: "%1."`, exactly the ODF `style:num-format="1" style:num-suffix="."` it was authored with; the bulleted list's own level 0 resolves to `format: "bullet"` with `text` carrying the exact single-character glyph LibreOffice wrote for it (`U+F0B7`, the Symbol/Wingdings-font Private Use Area bullet code point real Word-format producers use, not a printable Unicode bullet) -- confirmed byte-for-byte against the raw `PlfLst`/`LVL` bytes LibreOffice actually wrote, not assumed. Both lists' nine `LVL`s per `LSTF` (a real multi-level `LSTF`, `fSimpleList` clear) parse cleanly end to end with no bounds error, and each paragraph's own `list.numId`/`list.level` resolves through to the correct definition.
154
156
 
157
+ **The write side is verified by this package's own round trip, not yet against a third-party implementation.** `write.test.ts`'s own `describe("writeDocContent numbering")` block covers a multi-level list (`format`/`text` per level surviving through `readDocContent`), a bulleted list (its glyph, not a numbered placeholder), separate `ilfo` minting for separate `numId`s, a document with no lists at all (no `PlfLst`/`PlfLfo` written), and a list membership inside a table cell -- every case reading back through this package's own reader exactly as written. `list/numbering-write.test.ts` covers `buildNumberingTables` one layer down, independently of `gatherListUsage`: the `LSTF`/`LFO` byte shape a hand-built `NumberingDefinitions` produces, the empty-definitions-map case returning `undefined`, the numerically-colliding-key rejection described above, and that `gatherListUsage`'s own output can never trigger that rejection. Unlike the read side's LibreOffice cross-check above, no independent [MS-DOC] implementation has yet opened a `writeDocContent` numbering sample: that verification is the next thing worth doing here, mirroring how [Tables](#tables) and [Cell decoration](#cell-decoration) were each verified once a real producer's own file was available to compare against.
158
+
155
159
  ## Metadata
156
160
 
157
161
  A `.doc`'s title, author, and dates do not live in any [MS-DOC] structure at all — they live in a `"\x05SummaryInformation"` stream, a genuinely different format ([MS-OLEPS] Property Set Streams) that happens to sit beside `WordDocument`/`1Table` in the same [MS-CFB] compound file. `readDocContent` reads that stream when present (`archive-codec`'s `readSummaryInformation`, since the property-set format itself is zero document-format knowledge, exactly as the [MS-CFB] container it sits inside is) and maps it onto `document-schema.js`'s `LayoutMetadata` (`archive-codec`'s own `summaryInformationToLayoutMetadata` — the mapping is format-agnostic, so it lives there rather than being copied in this package, alongside `xls-codec`'s and `ppt-codec`'s identical need for it); `writeDocContent` does the inverse (`src/metadata.ts`'s `layoutMetadataToSummaryInformation`, which validates `createdIso`/`modifiedIso` as real dates and throws a `DocFormatError` naming the offending field before delegating to `archive-codec`'s own mapping — see [Writing](#writing)), including a `"\x05SummaryInformation"` stream in its `writeCompoundFile` call only when the input's metadata actually carries something that stream can hold — an input whose metadata is `{}`, or carries only fields the mapping below has no destination for, produces no stream at all, matching what an absent-metadata read already returns.
@@ -169,7 +173,7 @@ Only the fixed SummaryInformation property set is read or written — the siblin
169
173
 
170
174
  `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 spec-conformant style sheet carrying zero styles (`style/stsh.ts`'s `buildEmptyStsh` — `FibRgFcLcb97.lcbStshf` "MUST be a nonzero value", so a document is never written without one, even 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.
171
175
 
172
- 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`, `indentFirstLinePt`, `spacingBeforePt`, `spacingAfterPt`, `lineSpacing` (only `LSPD`'s multiplier form, matching the reader), and `pageBreakBefore`.
176
+ 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).
173
177
 
174
178
  **Deliberately not handled**, beyond what the read-side scope table above already states applies to both directions:
175
179
 
@@ -214,6 +218,7 @@ The modules layer in the order [MS-DOC]'s own algorithms chain:
214
218
  | `src/prop/sprm.ts` | `Sprm` decoding and the operand-size table that makes a grpprl walkable. |
215
219
  | `src/prop/fkp.ts` | The formatted disk pages and the bin tables that address them. |
216
220
  | `src/prop/chp.ts`, `src/prop/pap.ts` | Folding a grpprl into character and paragraph properties. |
221
+ | `src/list/numbering.ts` | Resolving `PlfLst`/`PlfLfo` into `NumberingDefinitions` (see [Numbering definitions](#numbering-definitions)). |
217
222
  | `src/style/stsh.ts` | The style sheet. |
218
223
  | `src/style/fonts.ts` | The font table (`SttbfFfn`/`FFN`) — read and write together, since both directions share one small, self-contained field layout. |
219
224
  | `src/metadata.ts` | Wraps `archive-codec`'s own `SummaryInformationProperties` <-> `LayoutMetadata` mapping with this package's `createdIso`/`modifiedIso` date validation, throwing `DocFormatError` for a malformed one rather than letting an opaque `RangeError` escape the FILETIME conversion (see [Metadata](#metadata)). |
@@ -224,6 +229,7 @@ The modules layer in the order [MS-DOC]'s own algorithms chain:
224
229
  | `src/fib/write.ts` | Builds a real FIB for nFib 0x00C1 (Word 97), populated with the fc/lcb pairs this package's own writer needs. |
225
230
  | `src/text/piece-table-write.ts` | Builds a `Clx` describing the whole logical text stream as one uncompressed piece. |
226
231
  | `src/prop/chp-write.ts`, `src/prop/pap-write.ts` | The inverse of `chp.ts`/`pap.ts`: a run's or paragraph's direct properties to a grpprl. |
232
+ | `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)). |
227
233
  | `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. |
228
234
  | `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. |
229
235
  | `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. |
@@ -277,7 +283,7 @@ Like every foundation and format-codec package in this family, `doc-codec`'s pub
277
283
 
278
284
  Every structure is tested against bytes hand-assembled from [MS-DOC]'s own field tables rather than dumped from a real Word file, and the read-side test-support builders (`src/test-support/`) place each field by adding up the specification's declared sizes while the parsers read them from independently derived constants — so the two agree only if both match the specification. `buildDoc` assembles a whole synthetic `.doc`: a real compound file, a real FIB, a real piece table, real FKP pages, and a real style sheet, wired together with the offsets a producer would compute.
279
285
 
280
- 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, 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.
286
+ 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.
281
287
 
282
288
  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.
283
289
 
@@ -36,6 +36,8 @@ function buildFib(spec) {
36
36
  pair(require_fib_offsets.FC_LCB_VALUE_INDEX.fcSttbfFfn, spec.fcSttbfFfn, spec.lcbSttbfFfn);
37
37
  pair(require_fib_offsets.FC_LCB_VALUE_INDEX.fcClx, spec.fcClx, spec.lcbClx);
38
38
  pair(require_fib_offsets.FC_LCB_VALUE_INDEX.fcPlcfSed, spec.fcPlcfSed, spec.lcbPlcfSed);
39
+ pair(require_fib_offsets.FC_LCB_VALUE_INDEX.fcPlfLst, spec.fcPlfLst, spec.lcbPlfLst);
40
+ pair(require_fib_offsets.FC_LCB_VALUE_INDEX.fcPlfLfo, spec.fcPlfLfo, spec.lcbPlfLfo);
39
41
  if (require_bytes.readUint16LE(bytes, 0) !== 42476) throw new require_errors.DocFormatError("buildFib produced a Fib whose own wIdent does not read back as 0xA5EC; this is an internal defect, not an input error");
40
42
  return bytes;
41
43
  }
@@ -15,6 +15,11 @@ interface FibWriteSpec {
15
15
  /** 0/0 when the document uses no font table (see write.ts). */
16
16
  readonly fcSttbfFfn: number;
17
17
  readonly lcbSttbfFfn: number;
18
+ /** 0/0 when the document uses no lists at all (see write.ts and list/numbering-write.ts) -- matching what list/numbering.ts's own readNumberingDefinitions treats as "no lists" on the read side. */
19
+ readonly fcPlfLst: number;
20
+ readonly lcbPlfLst: number;
21
+ readonly fcPlfLfo: number;
22
+ readonly lcbPlfLfo: number;
18
23
  }
19
24
  declare function buildFib(spec: FibWriteSpec): Uint8Array<ArrayBuffer>;
20
25
  //#endregion
@@ -15,6 +15,11 @@ interface FibWriteSpec {
15
15
  /** 0/0 when the document uses no font table (see write.ts). */
16
16
  readonly fcSttbfFfn: number;
17
17
  readonly lcbSttbfFfn: number;
18
+ /** 0/0 when the document uses no lists at all (see write.ts and list/numbering-write.ts) -- matching what list/numbering.ts's own readNumberingDefinitions treats as "no lists" on the read side. */
19
+ readonly fcPlfLst: number;
20
+ readonly lcbPlfLst: number;
21
+ readonly fcPlfLfo: number;
22
+ readonly lcbPlfLfo: number;
18
23
  }
19
24
  declare function buildFib(spec: FibWriteSpec): Uint8Array<ArrayBuffer>;
20
25
  //#endregion
package/dist/fib/write.js CHANGED
@@ -35,6 +35,8 @@ function buildFib(spec) {
35
35
  pair(FC_LCB_VALUE_INDEX.fcSttbfFfn, spec.fcSttbfFfn, spec.lcbSttbfFfn);
36
36
  pair(FC_LCB_VALUE_INDEX.fcClx, spec.fcClx, spec.lcbClx);
37
37
  pair(FC_LCB_VALUE_INDEX.fcPlcfSed, spec.fcPlcfSed, spec.lcbPlcfSed);
38
+ pair(FC_LCB_VALUE_INDEX.fcPlfLst, spec.fcPlfLst, spec.lcbPlfLst);
39
+ pair(FC_LCB_VALUE_INDEX.fcPlfLfo, spec.fcPlfLfo, spec.lcbPlfLfo);
38
40
  if (readUint16LE(bytes, 0) !== 42476) throw new DocFormatError("buildFib produced a Fib whose own wIdent does not read back as 0xA5EC; this is an internal defect, not an input error");
39
41
  return bytes;
40
42
  }
package/dist/index.cjs CHANGED
@@ -52,6 +52,7 @@ exports.FOOTNOTE_REFERENCE = require_text_special.FOOTNOTE_REFERENCE;
52
52
  exports.INLINE_PICTURE = require_text_special.INLINE_PICTURE;
53
53
  exports.LINE_BREAK = require_text_special.LINE_BREAK;
54
54
  exports.LW_OFFSET = require_fib_offsets.LW_OFFSET;
55
+ exports.NUMBER_FORMAT_BY_NFC = require_list_numbering.NUMBER_FORMAT_BY_NFC;
55
56
  exports.PARAGRAPH_MARK = require_text_special.PARAGRAPH_MARK;
56
57
  exports.PropertyBinTable = require_prop_fkp.PropertyBinTable;
57
58
  exports.SECTION_MARK = require_text_special.SECTION_MARK;
package/dist/index.d.cts CHANGED
@@ -10,18 +10,18 @@ import { Piece, PieceTable, characterOffset, characterSize, parseClx } from "./t
10
10
  import { buildTextClx } from "./text/piece-table-write.cjs";
11
11
  import { COMPRESSED_CHARACTER_MAP, TextRange, readTextRange } from "./text/characters.cjs";
12
12
  import { ANNOTATION_REFERENCE, CELL_MARK, DRAWN_OBJECT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, PARAGRAPH_MARK, SECTION_MARK, SYMBOL_ANCHOR, endsParagraph, isAnchorOnly } from "./text/special.cjs";
13
- import { a as operandSize, i as decodeSprm, n as SGC, o as readGrpprl, r as Sprm, t as Prl } from "./sprm-Djg5RNiq.cjs";
13
+ import { a as operandSize, i as decodeSprm, n as SGC, o as readGrpprl, r as Sprm, t as Prl } from "./sprm-DEyvdOo0.cjs";
14
14
  import { ChpxFkp, FKP_PAGE_SIZE, PapxFkp, PapxLookup, PapxRecord, PropertyBinTable, parseChpxFkp, parsePapxFkp } from "./prop/fkp.cjs";
15
15
  import { ChpxRunToWrite, PapxParagraphToWrite, buildChpxPages, buildPapxPages, buildPropertyBinTable, firstFcOfPage, fitsAloneOnPapxPage } from "./prop/fkp-write.cjs";
16
16
  import { CharacterProperties, applyCharacterSprms } from "./prop/chp.cjs";
17
17
  import { encodeCharacterGrpprl } from "./prop/chp-write.cjs";
18
- import { n as applyParagraphSprms, t as ParagraphProperties } from "./pap-BmelEYEP.cjs";
18
+ import { n as applyParagraphSprms, t as ParagraphProperties } from "./pap-BT1p0uzu.cjs";
19
19
  import { encodeParagraphGrpprl } from "./prop/pap-write.cjs";
20
20
  import { SectionProperties, applySectionSprms, readSectionProperties } from "./prop/sep.cjs";
21
21
  import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.cjs";
22
22
  import { STI_USER_DEFINED, STK, Style, StyleSheet, buildEmptyStsh, headingLevelFromIstd, parseStsh } from "./style/stsh.cjs";
23
23
  import { buildFontTable, parseFontTable } from "./style/fonts.cjs";
24
- import { i as readNumberingDefinitions, n as NumberingDefinitions, r as NumberingLevel, t as NumberingDefinition } from "./numbering-Cir-uGdc.cjs";
24
+ import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "./numbering-Bivayss4.cjs";
25
25
  import { DocContent, DocStreams, ParagraphEntry, readDocContent, readDocStreams } from "./read.cjs";
26
26
  import { WriteDocContentOptions, writeDocContent } from "./write.cjs";
27
- export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildEmptyStsh, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildTextClx, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, slice, tableStreamName, writeDocContent };
27
+ export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildEmptyStsh, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildTextClx, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, slice, tableStreamName, writeDocContent };
package/dist/index.d.ts CHANGED
@@ -10,18 +10,18 @@ import { Piece, PieceTable, characterOffset, characterSize, parseClx } from "./t
10
10
  import { buildTextClx } from "./text/piece-table-write.js";
11
11
  import { COMPRESSED_CHARACTER_MAP, TextRange, readTextRange } from "./text/characters.js";
12
12
  import { ANNOTATION_REFERENCE, CELL_MARK, DRAWN_OBJECT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, PARAGRAPH_MARK, SECTION_MARK, SYMBOL_ANCHOR, endsParagraph, isAnchorOnly } from "./text/special.js";
13
- import { a as operandSize, i as decodeSprm, n as SGC, o as readGrpprl, r as Sprm, t as Prl } from "./sprm-Djg5RNiq.js";
13
+ import { a as operandSize, i as decodeSprm, n as SGC, o as readGrpprl, r as Sprm, t as Prl } from "./sprm-DEyvdOo0.js";
14
14
  import { ChpxFkp, FKP_PAGE_SIZE, PapxFkp, PapxLookup, PapxRecord, PropertyBinTable, parseChpxFkp, parsePapxFkp } from "./prop/fkp.js";
15
15
  import { ChpxRunToWrite, PapxParagraphToWrite, buildChpxPages, buildPapxPages, buildPropertyBinTable, firstFcOfPage, fitsAloneOnPapxPage } from "./prop/fkp-write.js";
16
16
  import { CharacterProperties, applyCharacterSprms } from "./prop/chp.js";
17
17
  import { encodeCharacterGrpprl } from "./prop/chp-write.js";
18
- import { n as applyParagraphSprms, t as ParagraphProperties } from "./pap-BkFo8dZO.js";
18
+ import { n as applyParagraphSprms, t as ParagraphProperties } from "./pap-DOFLMWcG.js";
19
19
  import { encodeParagraphGrpprl } from "./prop/pap-write.js";
20
20
  import { SectionProperties, applySectionSprms, readSectionProperties } from "./prop/sep.js";
21
21
  import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.js";
22
22
  import { STI_USER_DEFINED, STK, Style, StyleSheet, buildEmptyStsh, headingLevelFromIstd, parseStsh } from "./style/stsh.js";
23
23
  import { buildFontTable, parseFontTable } from "./style/fonts.js";
24
- import { i as readNumberingDefinitions, n as NumberingDefinitions, r as NumberingLevel, t as NumberingDefinition } from "./numbering-DF1z1PyG.js";
24
+ import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "./numbering-Co4jssbG.js";
25
25
  import { DocContent, DocStreams, ParagraphEntry, readDocContent, readDocStreams } from "./read.js";
26
26
  import { WriteDocContentOptions, writeDocContent } from "./write.js";
27
- export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildEmptyStsh, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildTextClx, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, slice, tableStreamName, writeDocContent };
27
+ export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildEmptyStsh, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildTextClx, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, slice, tableStreamName, writeDocContent };
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import { applySectionSprms, readSectionProperties } from "./prop/sep.js";
21
21
  import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.js";
22
22
  import { STI_USER_DEFINED, STK, buildEmptyStsh, headingLevelFromIstd, parseStsh } from "./style/stsh.js";
23
23
  import { buildFontTable, parseFontTable } from "./style/fonts.js";
24
- import { readNumberingDefinitions } from "./list/numbering.js";
24
+ import { NUMBER_FORMAT_BY_NFC, readNumberingDefinitions } from "./list/numbering.js";
25
25
  import { readDocContent, readDocStreams } from "./read.js";
26
26
  import { writeDocContent } from "./write.js";
27
- export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DRAWN_OBJECT, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildEmptyStsh, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildTextClx, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, slice, tableStreamName, writeDocContent };
27
+ export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DRAWN_OBJECT, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildEmptyStsh, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildTextClx, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, slice, tableStreamName, writeDocContent };
@@ -0,0 +1,165 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
+ const require_list_numbering = require("./numbering.cjs");
4
+ //#region src/list/numbering-write.ts
5
+ const LSTF_SIZE = 28;
6
+ const LVLF_SIZE = 28;
7
+ const LFO_SIZE = 16;
8
+ const LSTF_FLAG_SIMPLE_LIST = 1;
9
+ /** The LVLF flags-byte bit numbering.ts's own reader treats as fNoRestart -- restated here for the reason pap-write.ts's own top comment gives for restating pap.ts's opcodes: this module's own byte layout is coupled to the specification's field table, not to a sibling module's private constant name. */
10
+ const LVLF_FLAG_NO_RESTART = 2;
11
+ /** A non-simple LSTF always carries exactly nine LVLs ([MS-DOC] 2.9.191); sprmPIlvl's own operand range this writer's caller (pap-write.ts) validates against is the same fact restated at the paragraph-property layer. */
12
+ const MAX_LIST_LEVEL = 8;
13
+ const LEVELS_PER_MULTI_LEVEL_LIST = 9;
14
+ /** The format every level this writer invents for a paragraph that leaves ContentListMembership.format unstated, and every level a multi-level list's own dense 0..8 run needs filling but no paragraph ever actually used -- an arbitrary but harmless choice, since an unused level's own appearance is never read back into a context that renders it. */
15
+ const DEFAULT_FORMAT = "decimal";
16
+ /** The glyph this writer states for format 'bullet'. A real Word-format producer typically uses a Private Use Area code point from a symbol font (the README's own "Numbering definitions" section records LibreOffice writing U+F0B7) -- this writer uses the plain, portable Unicode bullet instead, since this is a synthesised definition rather than a captured one, and it round-trips exactly through this package's own reader either way. */
17
+ const BULLET_GLYPH = "•";
18
+ /** The inverse of numbering.ts's own NUMBER_FORMAT_BY_NFC, restricted to whichever of its entries a format string can actually reach -- built once by inverting the single source of truth rather than hand-maintaining a second table that could silently drift from it. Where two nfc values map to the same format string (0x00 and 0x28 both mean "decimal"), the lower one wins, because Object.entries on an object whose own keys are non-negative integer strings iterates in ascending numeric order regardless of insertion order (the one case JavaScript's own key-ordering rules give a numeric guarantee), so the first entry visited for "decimal" is 0x00. */
19
+ const NFC_BY_FORMAT = (() => {
20
+ const byFormat = /* @__PURE__ */ new Map();
21
+ for (const [nfcKey, format] of Object.entries(require_list_numbering.NUMBER_FORMAT_BY_NFC)) if (!byFormat.has(format)) byFormat.set(format, Number(nfcKey));
22
+ return byFormat;
23
+ })();
24
+ function push16(bytes, value) {
25
+ bytes.push(value & 255, value >> 8 & 255);
26
+ }
27
+ function push32(bytes, value) {
28
+ const unsigned = value >>> 0;
29
+ bytes.push(unsigned & 255, unsigned >> 8 & 255, unsigned >> 16 & 255, unsigned >>> 24 & 255);
30
+ }
31
+ function writeUint32LE(target, offset, value) {
32
+ const unsigned = value >>> 0;
33
+ target[offset] = unsigned & 255;
34
+ target[offset + 1] = unsigned >> 8 & 255;
35
+ target[offset + 2] = unsigned >> 16 & 255;
36
+ target[offset + 3] = unsigned >>> 24 & 255;
37
+ }
38
+ function encodeXst(text) {
39
+ const bytes = [];
40
+ push16(bytes, text.length);
41
+ for (let index = 0; index < text.length; index += 1) push16(bytes, text.charCodeAt(index));
42
+ return bytes;
43
+ }
44
+ /** The exact inverse of numbering.ts's own readLevelText: for a numbered format, a single placeholder for the level's own zero-based index (encoded as a raw code unit, per Xst's own placeholder convention) followed by a literal '.', reproducing readLevelText's '%1.'-style output ('%N' where N = level+1) on a subsequent read; for 'bullet', the literal glyph with no placeholder at all. */
45
+ function buildLevelXst(level, format) {
46
+ if (format === "bullet") return {
47
+ xstText: BULLET_GLYPH,
48
+ positions: []
49
+ };
50
+ return {
51
+ xstText: `${String.fromCharCode(level)}.`,
52
+ positions: [1]
53
+ };
54
+ }
55
+ function levelText(level, format) {
56
+ return format === "bullet" ? BULLET_GLYPH : `%${level + 1}.`;
57
+ }
58
+ function defaultLevel(level, format) {
59
+ return {
60
+ format,
61
+ text: levelText(level, format),
62
+ startAt: 1
63
+ };
64
+ }
65
+ function gatherListUsage(memberships) {
66
+ const ilfoByNumId = /* @__PURE__ */ new Map();
67
+ const usedLevelsByIlfo = /* @__PURE__ */ new Map();
68
+ for (const membership of memberships) {
69
+ if (membership?.numId === void 0) continue;
70
+ if (membership.level > MAX_LIST_LEVEL) throw new require_errors.DocFormatError(`paragraph list numId ${JSON.stringify(membership.numId)} names level ${membership.level}, outside the 0..${MAX_LIST_LEVEL} range a non-simple LSTF's fixed nine LVLs ([MS-DOC] 2.9.191) can address`);
71
+ let ilfo = ilfoByNumId.get(membership.numId);
72
+ if (ilfo === void 0) {
73
+ ilfo = ilfoByNumId.size + 1;
74
+ ilfoByNumId.set(membership.numId, ilfo);
75
+ usedLevelsByIlfo.set(ilfo, /* @__PURE__ */ new Map());
76
+ }
77
+ const used = usedLevelsByIlfo.get(ilfo);
78
+ if (used === void 0) throw new require_errors.DocFormatError("internal defect: gatherListUsage minted an ilfo with no levels map of its own");
79
+ if (!used.has(membership.level)) used.set(membership.level, defaultLevel(membership.level, membership.format ?? DEFAULT_FORMAT));
80
+ }
81
+ const definitions = {};
82
+ for (const [ilfo, used] of usedLevelsByIlfo) {
83
+ const levelCount = Math.max(...used.keys()) === 0 ? 1 : LEVELS_PER_MULTI_LEVEL_LIST;
84
+ const levels = {};
85
+ for (let level = 0; level < levelCount; level += 1) levels[String(level)] = used.get(level) ?? defaultLevel(level, DEFAULT_FORMAT);
86
+ definitions[String(ilfo)] = { levels };
87
+ }
88
+ return {
89
+ definitions,
90
+ ilfoByNumId
91
+ };
92
+ }
93
+ function buildLstfBytes(lsid, fSimpleList) {
94
+ const lstf = new Array(LSTF_SIZE).fill(0);
95
+ writeUint32LE(lstf, 0, lsid);
96
+ lstf[26] = fSimpleList ? LSTF_FLAG_SIMPLE_LIST : 0;
97
+ return lstf;
98
+ }
99
+ function buildLvlBytes(level, numberingLevel) {
100
+ const nfc = NFC_BY_FORMAT.get(numberingLevel.format);
101
+ if (nfc === void 0) throw new require_errors.DocFormatError(`numbering level format ${JSON.stringify(numberingLevel.format)} has no [MS-OSHARED] 2.2.1.3 MSONFC mapping this writer can state -- only ${JSON.stringify([...NFC_BY_FORMAT.keys()])} round-trip through ContentListMembership.format`);
102
+ const { xstText, positions } = buildLevelXst(level, numberingLevel.format);
103
+ const lvlf = new Array(LVLF_SIZE).fill(0);
104
+ writeUint32LE(lvlf, 0, numberingLevel.startAt);
105
+ lvlf[4] = nfc;
106
+ if (numberingLevel.restart !== void 0) {
107
+ lvlf[5] = LVLF_FLAG_NO_RESTART;
108
+ lvlf[26] = numberingLevel.restart;
109
+ }
110
+ positions.forEach((position, index) => {
111
+ lvlf[6 + index] = position;
112
+ });
113
+ return [...lvlf, ...encodeXst(xstText)];
114
+ }
115
+ /** Encodes a NumberingDefinitions into real PlfLst/PlfLfo bytes -- undefined when it names no lists at all, so writeDocContent can skip both fc/lcb pairs entirely rather than writing an empty-but-present structure no paragraph ever references. Independent of gatherListUsage: any NumberingDefinitions this package's own numbering.ts could produce from a real .doc encodes here too, including a startAt other than 1 or a restart rule, since every LVLF field this function writes comes from the definition's own NumberingLevel rather than an assumed default. */
116
+ function buildNumberingTables(definitions) {
117
+ const keys = Object.keys(definitions);
118
+ const keyByIlfo = /* @__PURE__ */ new Map();
119
+ for (const key of keys) {
120
+ const ilfo = Number(key);
121
+ const collidingKey = keyByIlfo.get(ilfo);
122
+ if (collidingKey !== void 0) throw new require_errors.DocFormatError(`numbering definition keys ${JSON.stringify(collidingKey)} and ${JSON.stringify(key)} both name lsid ${ilfo} once converted to a number -- [MS-DOC] 2.9.147 requires lsid to be unique for each LSTF`);
123
+ keyByIlfo.set(ilfo, key);
124
+ }
125
+ const ilfos = [...keyByIlfo.keys()].sort((a, b) => a - b);
126
+ if (ilfos.length === 0) return void 0;
127
+ const lstfBytes = [];
128
+ const lvlBytes = [];
129
+ const rgLfoBytes = [];
130
+ for (const ilfo of ilfos) {
131
+ const definition = definitions[String(ilfo)];
132
+ if (definition === void 0) throw new require_errors.DocFormatError("internal defect: buildNumberingTables lost a definition for an ilfo its own key list just named");
133
+ const levelKeys = Object.keys(definition.levels).map(Number).sort((a, b) => a - b);
134
+ const fSimpleList = levelKeys.length === 1 && levelKeys[0] === 0;
135
+ const isDenseMultiLevel = levelKeys.length === LEVELS_PER_MULTI_LEVEL_LIST && levelKeys.every((level, index) => level === index);
136
+ if (!fSimpleList && !isDenseMultiLevel) throw new require_errors.DocFormatError(`numbering definition for ilfo ${ilfo} names levels ${JSON.stringify(levelKeys)}, but [MS-DOC] 2.9.191's own LSTF states either exactly level 0 alone (a simple list) or a dense 0..${MAX_LIST_LEVEL} run of all nine -- there is no partial shape to write`);
137
+ lstfBytes.push(...buildLstfBytes(ilfo, fSimpleList));
138
+ for (const level of levelKeys) {
139
+ const numberingLevel = definition.levels[String(level)];
140
+ if (numberingLevel === void 0) throw new require_errors.DocFormatError("internal defect: buildNumberingTables lost a level its own key list just named");
141
+ lvlBytes.push(...buildLvlBytes(level, numberingLevel));
142
+ }
143
+ const lfo = new Array(LFO_SIZE).fill(0);
144
+ writeUint32LE(lfo, 0, ilfo);
145
+ rgLfoBytes.push(...lfo);
146
+ }
147
+ const plfLstHeader = [];
148
+ push16(plfLstHeader, ilfos.length);
149
+ const plfLst = new Uint8Array([
150
+ ...plfLstHeader,
151
+ ...lstfBytes,
152
+ ...lvlBytes
153
+ ]);
154
+ const plfLfoHeader = [];
155
+ push32(plfLfoHeader, ilfos.length);
156
+ const plfLfo = new Uint8Array([...plfLfoHeader, ...rgLfoBytes]);
157
+ return {
158
+ plfLst,
159
+ lcbPlfLst: plfLstHeader.length + lstfBytes.length,
160
+ plfLfo
161
+ };
162
+ }
163
+ //#endregion
164
+ exports.buildNumberingTables = buildNumberingTables;
165
+ exports.gatherListUsage = gatherListUsage;
@@ -0,0 +1,21 @@
1
+ import { r as NumberingDefinitions } from "../numbering-Bivayss4.cjs";
2
+ import { ContentListMembership } from "document-schema.js";
3
+ //#region src/list/numbering-write.d.ts
4
+ /** One document's own numbering, gathered from every paragraph's list membership (the caller passes the already-flattened sequence, table cells included, so a list used only inside a table cell is still resolved). */
5
+ interface ListUsage {
6
+ /** Keyed by the freshly-minted ilfo, stringified -- the identical shape and key convention numbering.ts's own readNumberingDefinitions produces (its own numId IS the ilfo; see this module's own top comment), so re-reading the bytes buildNumberingTables encodes from this reproduces it. */
7
+ readonly definitions: NumberingDefinitions;
8
+ /** A paragraph's own ContentListMembership.numId to the ilfo minted for it -- what pap-write.ts's own encodeParagraphGrpprl needs to write that paragraph's sprmPIlfo. */
9
+ readonly ilfoByNumId: ReadonlyMap<string, number>;
10
+ }
11
+ declare function gatherListUsage(memberships: readonly (ContentListMembership | undefined)[]): ListUsage;
12
+ interface NumberingTables {
13
+ /** The whole PlfLst -- cLst, the LSTF array, AND its appended LVL array, physically contiguous. lcbPlfLst below is shorter than this: [MS-DOC]'s own PlfLst declares a length covering only cLst+the LSTF array, with the LVL array read past it (numbering.ts's own parsePlfLst comment) -- so the caller places all of `plfLst` at fcPlfLst but records `lcbPlfLst`, not `plfLst.length`, as the FIB's own lcbPlfLst. */
14
+ readonly plfLst: Uint8Array;
15
+ readonly lcbPlfLst: number;
16
+ readonly plfLfo: Uint8Array;
17
+ }
18
+ /** Encodes a NumberingDefinitions into real PlfLst/PlfLfo bytes -- undefined when it names no lists at all, so writeDocContent can skip both fc/lcb pairs entirely rather than writing an empty-but-present structure no paragraph ever references. Independent of gatherListUsage: any NumberingDefinitions this package's own numbering.ts could produce from a real .doc encodes here too, including a startAt other than 1 or a restart rule, since every LVLF field this function writes comes from the definition's own NumberingLevel rather than an assumed default. */
19
+ declare function buildNumberingTables(definitions: NumberingDefinitions): NumberingTables | undefined;
20
+ //#endregion
21
+ export { ListUsage, NumberingTables, buildNumberingTables, gatherListUsage };
@@ -0,0 +1,21 @@
1
+ import { r as NumberingDefinitions } from "../numbering-Co4jssbG.js";
2
+ import { ContentListMembership } from "document-schema.js";
3
+ //#region src/list/numbering-write.d.ts
4
+ /** One document's own numbering, gathered from every paragraph's list membership (the caller passes the already-flattened sequence, table cells included, so a list used only inside a table cell is still resolved). */
5
+ interface ListUsage {
6
+ /** Keyed by the freshly-minted ilfo, stringified -- the identical shape and key convention numbering.ts's own readNumberingDefinitions produces (its own numId IS the ilfo; see this module's own top comment), so re-reading the bytes buildNumberingTables encodes from this reproduces it. */
7
+ readonly definitions: NumberingDefinitions;
8
+ /** A paragraph's own ContentListMembership.numId to the ilfo minted for it -- what pap-write.ts's own encodeParagraphGrpprl needs to write that paragraph's sprmPIlfo. */
9
+ readonly ilfoByNumId: ReadonlyMap<string, number>;
10
+ }
11
+ declare function gatherListUsage(memberships: readonly (ContentListMembership | undefined)[]): ListUsage;
12
+ interface NumberingTables {
13
+ /** The whole PlfLst -- cLst, the LSTF array, AND its appended LVL array, physically contiguous. lcbPlfLst below is shorter than this: [MS-DOC]'s own PlfLst declares a length covering only cLst+the LSTF array, with the LVL array read past it (numbering.ts's own parsePlfLst comment) -- so the caller places all of `plfLst` at fcPlfLst but records `lcbPlfLst`, not `plfLst.length`, as the FIB's own lcbPlfLst. */
14
+ readonly plfLst: Uint8Array;
15
+ readonly lcbPlfLst: number;
16
+ readonly plfLfo: Uint8Array;
17
+ }
18
+ /** Encodes a NumberingDefinitions into real PlfLst/PlfLfo bytes -- undefined when it names no lists at all, so writeDocContent can skip both fc/lcb pairs entirely rather than writing an empty-but-present structure no paragraph ever references. Independent of gatherListUsage: any NumberingDefinitions this package's own numbering.ts could produce from a real .doc encodes here too, including a startAt other than 1 or a restart rule, since every LVLF field this function writes comes from the definition's own NumberingLevel rather than an assumed default. */
19
+ declare function buildNumberingTables(definitions: NumberingDefinitions): NumberingTables | undefined;
20
+ //#endregion
21
+ export { ListUsage, NumberingTables, buildNumberingTables, gatherListUsage };