xls-codec 4.14.0 → 4.15.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 (58) hide show
  1. package/README.md +37 -19
  2. package/dist/biff/record-writer.cjs +18 -0
  3. package/dist/biff/record-writer.d.cts +7 -1
  4. package/dist/biff/record-writer.d.ts +7 -1
  5. package/dist/biff/record-writer.js +18 -1
  6. package/dist/container.cjs +9 -1
  7. package/dist/container.d.cts +2 -0
  8. package/dist/container.d.ts +2 -0
  9. package/dist/container.js +9 -1
  10. package/dist/content.cjs +6 -5
  11. package/dist/content.js +6 -5
  12. package/dist/drawing/escher-constants.cjs +6 -0
  13. package/dist/drawing/escher-constants.d.cts +5 -1
  14. package/dist/drawing/escher-constants.d.ts +5 -1
  15. package/dist/drawing/escher-constants.js +5 -1
  16. package/dist/drawing/escher-writer.cjs +107 -0
  17. package/dist/drawing/escher-writer.d.cts +32 -0
  18. package/dist/drawing/escher-writer.d.ts +32 -0
  19. package/dist/drawing/escher-writer.js +105 -0
  20. package/dist/drawing/md4.cjs +146 -0
  21. package/dist/drawing/md4.d.cts +7 -0
  22. package/dist/drawing/md4.d.ts +7 -0
  23. package/dist/drawing/md4.js +145 -0
  24. package/dist/drawing-writer-Blm_NUSZ.d.cts +19 -0
  25. package/dist/drawing-writer-Blm_NUSZ.d.ts +19 -0
  26. package/dist/index.cjs +1 -0
  27. package/dist/index.d.cts +2 -2
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +2 -2
  30. package/dist/workbook/comments.cjs +26 -0
  31. package/dist/workbook/comments.d.cts +7 -1
  32. package/dist/workbook/comments.d.ts +7 -1
  33. package/dist/workbook/comments.js +26 -1
  34. package/dist/workbook/conditional-format-write.cjs +307 -18
  35. package/dist/workbook/conditional-format-write.js +307 -18
  36. package/dist/workbook/drawing-writer.cjs +253 -0
  37. package/dist/workbook/drawing-writer.d.cts +2 -0
  38. package/dist/workbook/drawing-writer.d.ts +2 -0
  39. package/dist/workbook/drawing-writer.js +252 -0
  40. package/dist/workbook/drawing.cjs +24 -3
  41. package/dist/workbook/drawing.d.cts +2 -0
  42. package/dist/workbook/drawing.d.ts +2 -0
  43. package/dist/workbook/drawing.js +25 -4
  44. package/dist/workbook/embedded-object.cjs +43 -0
  45. package/dist/workbook/embedded-object.d.cts +8 -0
  46. package/dist/workbook/embedded-object.d.ts +8 -0
  47. package/dist/workbook/embedded-object.js +41 -0
  48. package/dist/workbook/globals-writer.cjs +1 -0
  49. package/dist/workbook/globals-writer.d.cts +2 -0
  50. package/dist/workbook/globals-writer.d.ts +2 -0
  51. package/dist/workbook/globals-writer.js +2 -1
  52. package/dist/workbook/sheet-writer.cjs +4 -3
  53. package/dist/workbook/sheet-writer.d.cts +3 -2
  54. package/dist/workbook/sheet-writer.d.ts +3 -2
  55. package/dist/workbook/sheet-writer.js +4 -3
  56. package/dist/write.cjs +17 -7
  57. package/dist/write.js +17 -7
  58. package/package.json +1 -1
package/README.md CHANGED
@@ -26,28 +26,29 @@ Under active development, with real, tested **read and write** support. Built an
26
26
  - **Per-cell fonts** — a cell's own font (`ContentSheetCell.font`: bold/italic/underline/strike, family, size, colour), read from the Font record its XF's own `ifnt` indexes into the workbook's font table and written back into an interned font table the writer builds from the cells it writes (`biff/font.ts` holds both directions of the one record layout). A cell's font is derived by diffing that entry against the table's own first font — the Normal style's, which is what "the format's default" concretely means for a given file — so only properties the cell genuinely differs in are stated, the same default-omission policy alignment and fill already apply.
27
27
  - **Defined names** — a workbook's own defined names, read from and written to the `Lbl` records ([MS-XLS] 2.4.150) of the globals substream (`workbook/defined-names.ts`): the name as spelled (a built-in under its `_xlnm.` spelling), the `refersTo` rebuilt from the record's compiled Ptg stream by the same parser a cell formula uses, and a sheet-local scope carried as `scopeSheetIndex` translated into the document's own sheets-array position. The two print built-ins stay where they already live, on print settings.
28
28
  - **Data validation, read-only (ExaDev/documents.js#1098)** — a sheet's own `Dv` records (`src/workbook/data-validation.ts`) into `ContentSheet.dataValidations`: type, comparison operator, one or two formulas (via the same `Ptg` reader `Formula` records already use), allow-blank/show-input/show-error flags, error style, and prompt/error titles and messages, resolved directly against [MS-XLS]'s own published `Dv`/`DVal`/`DVParsedFormula`/`SqRefU` field layouts rather than a producer-specific convention.
29
- - **Charts, drawings, and images, read-only (ExaDev/documents.js#924)** — a worksheet's own MS-ODRAW (Escher) drawing layer (`src/drawing/escher.ts`, `src/drawing/shapes.ts`, `src/drawing/blips.ts`), the workbook-wide Blip Store every picture shape's own `pib` property indexes into, and an embedded chart's own nested substream (`src/workbook/chart.ts`), joined into `ContentSheet.images` and `ContentSheet.embeddedObjects` by `src/workbook/drawing.ts` see "Charts, drawings, and images, resolved for real" under Read-side gaps below.
29
+ - **Conditional formatting, the CF12-era spellings, written (ExaDev/documents.js#1186)** — every rule variant the schema models beyond `cellIs` writes as one CondFmt12 plus one CF12 record (`workbook/conditional-format-write.ts`): the text family as genuine `ct` 0x02 formula conditions in Excel's own generated shape (the search text as the formula's `PtgStr` operand, the identical spelling the reader's `readCfTextFilterRule` recovers it from), the operand-free family, `top10`, `aboveAverage`, `timePeriod`, and the three visual-scale rules (`colorScale`/`dataBar`/`iconSet`) through their CFGradient/CFDatabar/CFMultistate payloads with CFColor RGB triples and CFVO thresholds compiled through the same `compileFormulaText` cell formulas use. A `cellIs` rule still writes as a base CondFmt/CF pair; the two families share one sheet, base groups first. `ipriority` is minted (smallest unused positive integer) when a rule states no `priority`, since [MS-XLS] 2.4.43 requires it present and unique. What genuinely has no BIFF8 spelling throws a `BiffWriteError` naming it — see the Writer scope table below.
30
+ - **Charts, drawings, and images, read; images and non-chart embedded objects, now also written (ExaDev/documents.js#924, #1186)** — a worksheet's own MS-ODRAW (Escher) drawing layer (`src/drawing/escher.ts`, `src/drawing/shapes.ts`, `src/drawing/blips.ts`), the workbook-wide Blip Store every picture shape's own `pib` property indexes into, and an embedded chart's own nested substream (`src/workbook/chart.ts`), joined into `ContentSheet.images` and `ContentSheet.embeddedObjects` by `src/workbook/drawing.ts` — see "Charts, drawings, and images, resolved for real" under Read-side gaps below. The write direction (`src/drawing/escher-writer.ts`, `src/workbook/drawing-writer.ts`) builds the identical Escher container tree back out — the workbook-wide Blip Store deduplicated by image bytes, a per-sheet shape tree, and the `Obj` records pairing each shape with what it holds — and a non-chart embedded object's own document rides inside a genuine [MS-XLS] Embedding Storage (`src/workbook/embedded-object.ts`); see [Images and embedded objects, written](#images-and-embedded-objects-written).
30
31
 
31
32
  Verified primarily by round trip (`src/write.test.ts`, plus a dedicated `test/workers/write.test.ts` proving the whole write path inside a real `workerd` isolate, not just Node): build a `ContentDocument`, write it, read it back through this package's own independently-pinned reader, and check the result. Every record's own byte layout is additionally cited to its [MS-XLS] section in the writer's source, matching the reader's own convention.
32
33
 
33
34
  ### Writer scope
34
35
 
35
- What `writeXlsContent`/`writeXls` cover: every `ContentCellValue` kind a real `.xls` can hold (`number`, `percentage`, `currency`, `boolean`, `date`, `time`, `dateTime`, `string`, `error`; an `empty` cell is written as a `Blank` record when it carries formatting and as nothing at all when it does not — see below), merged ranges (`colSpan`/`rowSpan`), row heights and hidden rows, column widths and hidden columns, multiple sheets, explicit and default number formats, a shared string table deduplicated across the whole workbook, every field of a sheet's own [print settings](#print-settings), a cell's own [alignment](#cell-alignment), a cell's own background fill and per-side borders (see [Cell decoration](#cell-decoration)), a same-sheet formula (see [Formula writing](#formula-writing)), and a cell's own comment text and author (see [Cell comments, written](#cell-comments-written)), a sheet's own data validations (one Dval wrapper plus one Dv record per rule, every schema field through the same `compileFormulaText` formula compiler cell formulas use), and a sheet's own `cellIs` conditional formats (one CondFmt plus one CF record per rule, its style's text colour and solid background encoded as a DXFN through the workbook's own palette). What it deliberately does not:
36
-
37
- | Not written | Why |
38
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
- | `Formula` records for a 3D (cross-sheet or external-workbook) reference, an array-constant literal or CSE array formula, a defined name, or a call to a function outside `Ftab`'s own vocabulary | Same-sheet formulas write (see [Formula writing](#formula-writing)); these four constructs each throw a `BiffWriteError` naming the construct instead, rather than emit a token stream the writer cannot prove round-trips. A 3D reference needs a `SupBook`/`ExternSheet` pair this writer only ever mints today for the two built-in print-settings names (see [Print settings](#print-settings)), not for an arbitrary formula; an array constant/CSE formula needs a `PtgExtraArray`/`Array`-record trailer this writer does not build; a defined name has nowhere to resolve against, the same gap the `Lbl` row below describes; and Excel 2007+ added many worksheet functions BIFF8's own `Ftab` enumeration never named, resolved through a `PtgNameX`/add-in mechanism this writer does not implement. |
40
- | Per-cell font | The reader does not read one back: `ContentSheetCell` has no cell-level font field at all (`ooxml.js`'s xlsx reader makes the identical font-scope choice), so writing a real value here would be unverifiable by round trip. Every `XF` this writer emits still references the same single font. |
41
- | `MulBlank`/`RK`/`MulRk` | Pure compaction optimisations over information a plain `Blank`/`Number`/`LabelSst`/`BoolErr` record already carries losslessly. (`Blank` itself _is_ written, for a formatted empty cell — see the `empty` row below.) |
42
- | An `empty`-kind cell carrying no formatting | Written as nothing at all, which is what round-trips: `content.ts`'s reader drops an unformatted blank cell it reads, and a merged range's empty anchor is independently reconstructed from `MergeCells` alone. A formatted one (a fill, a border, or a non-default alignment) is the opposite case — its formatting exists only in the `XF` a cell record names, so it gets a real `Blank` record ([MS-XLS] 2.4.20) and round-trips with that formatting intact. |
43
- | Images, embedded objects (charts/drawings) | The reader now recovers a sheet's own pictures, charts, and generic drawing shapes (see Read-side gaps below), but writing them back out is its own separate mechanism a shape's own MS-ODRAW (Escher) container tree, a workbook-wide Blip Store, and (for a chart) a whole nested `BOF`/`EOF` substream, none of which this writer's existing per-record emission has any machinery for scoped as its own piece of work rather than folded into recovering the read side, the same split this table already draws for `Formula` records. `ContentSheet.images` and `ContentSheet.embeddedObjects` are silently ignored on write. |
44
- | Defined names (`Lbl`) | Read only for the two built-in ones a sheet's print range and repeated header bands live in ([Print settings](#print-settings)); a user-defined name has nowhere to land in `document-schema.js`'s spreadsheet model, so there is no round trip to verify a writer for it against. |
45
- | A data validation whose formula text `compileFormulaText` cannot encode | Every other rule shape writes (see the lead paragraph); a formula naming a construct outside the same-sheet `Ptg` vocabulary the cell-formula writer supports throws the identical `BiffWriteError` that cell formula already does, rather than emit a token stream this writer cannot prove round-trips. A comparison-typed rule carrying no operator, or a two-operand operator (`between`/`notBetween`) carrying fewer or more than its two formulas, is a malformed model and throws naming it. |
46
- | Conditional formatting beyond the base `cellIs` comparison (`CondFmt12`/`CF12`/`CFEx`) | A `cellIs` rule writes as one `CondFmt` plus one `CF` record -- its operator, both formula operands through `compileFormulaText`, and its style's text colour and solid background as a `DXFN` resolved through the workbook's own palette (`workbook/conditional-format-write.ts`, the write-side inverse of `readCondFmtGroup`/`readCf`/`parseDxfStyle`). Every other variant the schema models (colour scale, data bar, icon set, the text family, `top10`, ...) is a CF12-era rule with no base-CF spelling, and CF12 writing is its own mechanism this writer does not build -- such a rule throws a `BiffWriteError` naming its type rather than being silently dropped, the identical refusal the four out-of-scope formula constructs already draw. A `cellIs` rule's `priority`/`stopIfTrue`/`source` have no base-CF field and are not written. |
47
- | A comment's `replies` or `createdAt` | A cell's `text` and `author` write (see the Status list above); legacy BIFF8's `Note`/`Obj`/`Txo` triple has no threading and no per-comment timestamp at all, the same gap `src/workbook/comments.ts`'s own read side already documents, so there is nothing for these two fields to round-trip against. |
48
- | A page size no `iPaperSize` code names | Written as [MS-XLS] 2.4.257's own custom-paper value rather than as a named paper it is not — the dimensions themselves are unwritable, since `Setup` addresses paper only by code. See [Print settings](#print-settings). |
49
- | `Window1`/`Window2`, `CodePage`, `Index`/`DBCell`, the legacy interface records (`InterfaceHdr`, `WriteAccess`, …) | UI and interoperability bookkeeping [MS-XLS]'s own grammar names in the globals/worksheet substreams alongside the content-carrying records above, not data. `Index`/`DBCell` specifically is a pure cell-lookup performance optimisation (see [MS-XLS]'s own "Retrieval of Last-Calculated Cell Values Without Loading Cell Table") that this reader — and Excel's own reader — does not require to find a cell; real, well-established minimal BIFF8 writers (e.g. Python's `xlwt`) omit the same set and produce files Excel opens correctly. The calculation-state records (`CalcCount`, `CalcRefMode`, `CalcIter`, `CalcDelta`, `CalcSaveRecalc`) sat in this row until print settings needed them — see [Print settings](#print-settings) for why the writer emits them now. |
50
- | `Continue`-chain splitting | A record whose data would exceed the 8224-byte single-record ceiling ([MS-XLS] 2.1.4) — an extremely long shared string, an enormous shared string table, or thousands of merged ranges in one sheet — is refused with a thrown `BiffWriteError` rather than silently split across `Continue` records. |
36
+ What `writeXlsContent`/`writeXls` cover: every `ContentCellValue` kind a real `.xls` can hold (`number`, `percentage`, `currency`, `boolean`, `date`, `time`, `dateTime`, `string`, `error`; an `empty` cell is written as a `Blank` record when it carries formatting and as nothing at all when it does not — see below), merged ranges (`colSpan`/`rowSpan`), row heights and hidden rows, column widths and hidden columns, multiple sheets, explicit and default number formats, a shared string table deduplicated across the whole workbook, every field of a sheet's own [print settings](#print-settings), a cell's own [alignment](#cell-alignment), a cell's own background fill and per-side borders (see [Cell decoration](#cell-decoration)), a same-sheet formula (see [Formula writing](#formula-writing)), a cell's own comment text and author (see [Cell comments, written](#cell-comments-written)), a sheet's own images and non-chart embedded objects (see [Images and embedded objects, written](#images-and-embedded-objects-written)), a sheet's own data validations (one Dval wrapper plus one Dv record per rule, every schema field through the same `compileFormulaText` formula compiler cell formulas use), and a sheet's own conditional formats in full: a `cellIs` rule as one CondFmt plus one CF record (its style's text colour and solid background encoded as a DXFN through the workbook's own palette), every other variant the schema models as one CondFmt12 plus one CF12 record (the CF12-era spelling, its thresholds, template parameters, and CFColor triples written as the exact inverse of the reader's own walk). What it deliberately does not:
37
+
38
+ | Not written | Why |
39
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40
+ | `Formula` records for a 3D (cross-sheet or external-workbook) reference, an array-constant literal or CSE array formula, a defined name, or a call to a function outside `Ftab`'s own vocabulary | Same-sheet formulas write (see [Formula writing](#formula-writing)); these four constructs each throw a `BiffWriteError` naming the construct instead, rather than emit a token stream the writer cannot prove round-trips. A 3D reference needs a `SupBook`/`ExternSheet` pair this writer only ever mints today for the two built-in print-settings names (see [Print settings](#print-settings)), not for an arbitrary formula; an array constant/CSE formula needs a `PtgExtraArray`/`Array`-record trailer this writer does not build; a defined name has nowhere to resolve against, the same gap the `Lbl` row below describes; and Excel 2007+ added many worksheet functions BIFF8's own `Ftab` enumeration never named, resolved through a `PtgNameX`/add-in mechanism this writer does not implement. |
41
+ | Per-cell font | The reader does not read one back: `ContentSheetCell` has no cell-level font field at all (`ooxml.js`'s xlsx reader makes the identical font-scope choice), so writing a real value here would be unverifiable by round trip. Every `XF` this writer emits still references the same single font. |
42
+ | `MulBlank`/`RK`/`MulRk` | Pure compaction optimisations over information a plain `Blank`/`Number`/`LabelSst`/`BoolErr` record already carries losslessly. (`Blank` itself _is_ written, for a formatted empty cell — see the `empty` row below.) |
43
+ | An `empty`-kind cell carrying no formatting | Written as nothing at all, which is what round-trips: `content.ts`'s reader drops an unformatted blank cell it reads, and a merged range's empty anchor is independently reconstructed from `MergeCells` alone. A formatted one (a fill, a border, or a non-default alignment) is the opposite case — its formatting exists only in the `XF` a cell record names, so it gets a real `Blank` record ([MS-XLS] 2.4.20) and round-trips with that formatting intact. |
44
+ | A `'chart'`-kind embedded object | Writing one means embedding a genuine BIFF8 chart substream the whole [MS-XLS] chart grammar its series data links drive which is a chart engine of its own rather than a container for the flattened series table the schema carries; `writeXlsContent` throws `BiffWriteError` naming it rather than approximating a chart no BIFF8 record could actually redraw. A sheet's own images and every other embedded-object kind (`wordprocessing`/`presentation`/`spreadsheet`/`drawing`/`formula`) do write see [Images and embedded objects, written](#images-and-embedded-objects-written). |
45
+ | Defined names (`Lbl`) | Read only for the two built-in ones a sheet's print range and repeated header bands live in ([Print settings](#print-settings)); a user-defined name has nowhere to land in `document-schema.js`'s spreadsheet model, so there is no round trip to verify a writer for it against. |
46
+ | A data validation whose formula text `compileFormulaText` cannot encode | Every other rule shape writes (see the lead paragraph); a formula naming a construct outside the same-sheet `Ptg` vocabulary the cell-formula writer supports throws the identical `BiffWriteError` that cell formula already does, rather than emit a token stream this writer cannot prove round-trips. A comparison-typed rule carrying no operator, or a two-operand operator (`between`/`notBetween`) carrying fewer or more than its two formulas, is a malformed model and throws naming it. |
47
+ | The conditional-format shapes the CF12 record vocabulary itself cannot state | Every rule variant the schema models now writes (`workbook/conditional-format-write.ts`): a `cellIs` rule as one `CondFmt` plus one `CF` record -- its operator, both formula operands through `compileFormulaText`, and its style's text colour and solid background as a `DXFN` resolved through the workbook's own palette, the write-side inverse of `readCondFmtGroup`/`readCf`/`parseDxfStyle` -- and every other variant as one `CondFmt12` plus one `CF12` record, the CF12-era spelling (`ct` 0x02 formula condition carrying the search text for the text family, `ct` 0x05 filter templates for the operand-free/`top10`/`aboveAverage`/`timePeriod` family, `CFGradient`/`CFDatabar`/`CFMultistate` for the visual-scale rules), with an absent `priority` minted the smallest unused `ipriority` because [MS-XLS] 2.4.43 requires the field present and unique. What the record vocabulary genuinely diverges from the schema on throws a `BiffWriteError` naming it rather than being silently dropped or approximated: a `timePeriod` naming `thisYear`/`lastYear`/`nextYear` (ODF's own calcext extension values -- no `icfTemplate` names a year-scoped period); an `iconSetType` outside [MS-XLS] 2.5.36's seventeen built-in sets (a producer-extensible vocabulary with no `iIconSet` byte); an icon-set threshold count disagreeing with the set the rule names (`cStates` is pinned to the set's own icon count); `stopIfTrue` on a colour-scale/data-bar/icon-set rule (2.4.43 pins `fStopIfTrue` to zero for the visual-scale types); an `aboveAverage` standard-deviation count above 2 (2.5.23's `iParam` table admits 0/1/2); and two rules declaring the same `priority` (contradictory ordering rather than something to renumber). A `cellIs` rule's own `priority`/`stopIfTrue`/`source` still have no base-CF field and are not written; the CFEx compatibility spelling -- a legacy CF-plus-extension pair keeping a pre-2007 Excel able to evaluate a rule -- is not written either, the CF12 spelling being what this package's own reader resolves either way. |
48
+ | A comment's `replies` or `createdAt` | A cell's `text` and `author` write (see the Status list above); legacy BIFF8's `Note`/`Obj`/`Txo` triple has no threading and no per-comment timestamp at all, the same gap `src/workbook/comments.ts`'s own read side already documents, so there is nothing for these two fields to round-trip against. |
49
+ | A page size no `iPaperSize` code names | Written as [MS-XLS] 2.4.257's own custom-paper value rather than as a named paper it is not — the dimensions themselves are unwritable, since `Setup` addresses paper only by code. See [Print settings](#print-settings). |
50
+ | `Window1`/`Window2`, `CodePage`, `Index`/`DBCell`, the legacy interface records (`InterfaceHdr`, `WriteAccess`, …) | UI and interoperability bookkeeping [MS-XLS]'s own grammar names in the globals/worksheet substreams alongside the content-carrying records above, not data. `Index`/`DBCell` specifically is a pure cell-lookup performance optimisation (see [MS-XLS]'s own "Retrieval of Last-Calculated Cell Values Without Loading Cell Table") that this reader — and Excel's own reader — does not require to find a cell; real, well-established minimal BIFF8 writers (e.g. Python's `xlwt`) omit the same set and produce files Excel opens correctly. The calculation-state records (`CalcCount`, `CalcRefMode`, `CalcIter`, `CalcDelta`, `CalcSaveRecalc`) sat in this row until print settings needed them — see [Print settings](#print-settings) for why the writer emits them now. |
51
+ | `Continue`-chain splitting | A record whose data would exceed the 8224-byte single-record ceiling ([MS-XLS] 2.1.4) — an extremely long shared string, an enormous shared string table, or thousands of merged ranges in one sheet — is refused with a thrown `BiffWriteError` rather than silently split across `Continue` records. |
51
52
 
52
53
  Column widths round-trip to the nearest pixel Excel's own integer-pixel-grid quantization allows (matching the read direction's own "honestly approximate" contract, `units.ts`), never narrower than requested. A `.xls` cell outside BIFF8's own grid (65536 rows, 256 columns) is refused rather than silently wrapped or truncated.
53
54
 
@@ -68,7 +69,7 @@ Each deliberate rather than overlooked:
68
69
  - **Not read at all:** a `CFEx` record that extends a genuine `CF12` record rather than a legacy `CF` (see above). Defined names (`Lbl`) are read only for the two built-in ones a sheet's print range and repeated header bands live in ([Print settings](#print-settings)); a user-defined name has nowhere to land in `document-schema.js`'s spreadsheet model, so it is skipped.
69
70
  - **RC4-encrypted and XOR-obfuscated workbooks, decrypted for real (ExaDev/documents.js#1108, #922).** A `FilePass` record ([MS-XLS] 2.4.117) means every record after it is ciphertext under one of three schemes; this reader implements two of them, dispatching on the record's own `wEncryptionType` -- the [MS-OFFCRYPTO] 2.3.6.1 "RC4 encryption header" and 2.3.7's own XOR obfuscation (Method 1) -- `readXlsContent`/`readXls` take an optional `password`, verify it against the header's own fields (RC4's `EncryptedVerifier`/`EncryptedVerifierHash`, decrypted then compared; XOR obfuscation's `key`/`verificationBytes`, plain unencrypted checksums compared directly), then decrypt every subsequent record's data (`src/workbook/encryption.ts`) using the RC4/MD5 and XOR-obfuscation primitives [`archive-codec`](../archive-codec/README.md#xor-obfuscation-xlsdoc) shares with `doc-codec`. XOR obfuscation's own per-record `XorArrayIndex` -- `(streamOffset + recordDataLength) % 16`, `recordDataLength` always the record's own full declared size regardless of how much of a given span is actually being decrypted -- is confirmed against a genuine Excel-generated fixture, not just the published spec text (see `archive-codec`'s own account of why that spec text alone is not trustworthy here). [MS-XLS] 2.2.10's own exclusion list -- `BOF`, `FilePass`, and four shared-workbook revision-tracking records this package otherwise never reads, plus `BoundSheet8`'s own `lbPlyPos` field specifically -- is honoured exactly for both schemes, re-derived independently in this package's own tests rather than trusted by construction. A missing or incorrect password throws rather than returning a garbled document; the newer "RC4 CryptoAPI encryption header" scheme remains explicitly out of scope (ppt-codec's own encryption, tracked separately on [#1116](https://github.com/ExaDev/documents.js/issues/1116)).
70
71
 
71
- This package is wired into `documents.js`'s conversion registry (`xlsToPdf`/`pdfToXls`, `convertDocument("xls", ...)`, and every same-variant spreadsheet bridge) — see that package's own README Fidelity table for exactly which pairs route and which don't. Per-cell font is the one remaining read+write scope gap that is genuinely permanent rather than pending (see the Writer scope table below); formula writing, cell comments, data validation writing, and `cellIs` conditional-format writing are covered, each within the boundary the sections and the Writer scope table describe. Images/embedded objects (their MS-ODRAW/Escher container trees and Blip Store) and defined names remain read-only the remaining [#971](https://github.com/ExaDev/documents.js/issues/971) scope.
72
+ This package is wired into `documents.js`'s conversion registry (`xlsToPdf`/`pdfToXls`, `convertDocument("xls", ...)`, and every same-variant spreadsheet bridge) — see that package's own README Fidelity table for exactly which pairs route and which don't. Per-cell font, defined names, and a foreign (non-ISO-code) currency symbol are the remaining read+write scope gaps that are genuinely permanent rather than pending (see the Writer scope table below); formula writing, cell comments, data validation writing, conditional-format writing in full (base `cellIs` and the CF12-era variants alike), and a sheet's own images and non-chart embedded objects are all covered, each within the boundary the sections and the Writer scope table describe. A `'chart'`-kind embedded object is the one drawing-layer construct that still throws rather than writes see [Images and embedded objects, written](#images-and-embedded-objects-written).
72
73
 
73
74
  ## Cell decoration
74
75
 
@@ -116,12 +117,28 @@ Verified by round trip (`src/write.test.ts`'s own `formula records` suite) rathe
116
117
 
117
118
  ## Cell comments, written
118
119
 
119
- `src/workbook/comment-writer.ts` writes a `ContentSheetCellComment` back out as the same `Note`/`Obj`/`Txo` triple `src/workbook/comments.ts` already reads (see that module's own top comment for the full citation of how the three record kinds join): one `Note` record per commented cell naming its own `Obj` record by object id, that `Obj` record's `FtCmo`+`FtNts` pair marking it a comment, and a `Txo` record plus one `Continue` record carrying the comment's own text and a minimal, unformatted `TxORuns` trailer ([MS-XLS]'s own "`cbRuns` MUST be >= 16 and a multiple of 8" rule needs at least one real run plus its terminating `TxOLastRun`, even for plain text with no rich formatting). Object ids are assigned sequentially per sheet, starting at 1 — safe because this writer emits no other kind of `Obj` record yet (no shapes, charts, or form controls see [Writer scope](#writer-scope)).
120
+ `src/workbook/comment-writer.ts` writes a `ContentSheetCellComment` back out as the same `Note`/`Obj`/`Txo` triple `src/workbook/comments.ts` already reads (see that module's own top comment for the full citation of how the three record kinds join): one `Note` record per commented cell naming its own `Obj` record by object id, that `Obj` record's `FtCmo`+`FtNts` pair marking it a comment, and a `Txo` record plus one `Continue` record carrying the comment's own text and a minimal, unformatted `TxORuns` trailer ([MS-XLS]'s own "`cbRuns` MUST be >= 16 and a multiple of 8" rule needs at least one real run plus its terminating `TxOLastRun`, even for plain text with no rich formatting). Object ids are assigned sequentially per sheet, starting at 1 — unique within the substream because `workbook/drawing-writer.ts`'s own picture and embedded-object `Obj` records continue numbering from one past this count, rather than starting over at 1 themselves (see [Images and embedded objects, written](#images-and-embedded-objects-written)), matching [MS-XLS] 2.5.92's own "id MUST be unique among all Obj records of the substream" rule.
120
121
 
121
122
  Only `text` and `author` round-trip: legacy BIFF8's `NoteSh` carries no reply structure and no per-comment timestamp at all (unlike xlsx's own `[MS-XLSX]` threaded-comments extension), so `replies` and `createdAt` have nowhere to write to, the identical gap the reader's own [Read-side gaps](#read-side-gaps) section documents. A comment with no recorded `author` writes `stAuthor` as an empty string rather than a non-empty placeholder — `NoteSh`'s own field documents a length-1 minimum, but this package's reader only ever promotes a _non-empty_ `stAuthor` to `ContentSheetCellComment.author`, so a placeholder would round-trip back as a fabricated author nobody wrote; an empty string is the one spelling that round-trips as "no author" through this reader specifically. Rich per-character formatting within a comment (`TxORuns`' own genuine run array) is never written, matching the reader's own identical scope limit.
122
123
 
123
124
  Verified by round trip (`src/write.test.ts`'s own `cell comments` suite): a comment's text and author on a valued cell, a comment anchored to an otherwise-empty cell, a comment with no author, an empty-text comment, and several comments on one sheet each keeping their own cell and text.
124
125
 
126
+ ## Images and embedded objects, written
127
+
128
+ `src/drawing/escher-writer.ts` and `src/workbook/drawing-writer.ts` are the write direction of `src/drawing/escher.ts`/`src/drawing/shapes.ts`/`src/drawing/blips.ts` and `src/workbook/drawing.ts` (see "Charts, drawings, and images, resolved for real" under [Read-side gaps](#read-side-gaps)): one MS-ODRAW (Escher) container tree per sheet, the workbook-wide drawing group every sheet's Blip Store references share, and the `Obj` records pairing each Escher shape with what it actually holds.
129
+
130
+ `buildDrawingWritePlan` runs once, workbook-wide, before any sheet's own records are written — the same shape every other cross-sheet table in this writer takes (the number-format, colour, font, and shared-string plans in `write.ts`). A `ContentSheetImage` resolves into the workbook's own Blip Store (`OfficeArtBstoreContainer`): two images sharing identical base64 bytes dedupe onto one `BSE` entry, whose `cRef` counts the references, rather than minting a second copy of the same picture. `rgbUid`, the BSE's own hash of the blip's literal file bytes, is computed with a hand-written MD4 implementation (`src/drawing/md4.ts`, pinned against RFC 1320's own test vectors) — no platform crypto API exposes MD4, the digest [MS-ODRAW] itself specifies for this field. Only PNG and JPEG blips write (`ContentImageBlockSchema.format`'s other members have no `MSOBLIPTYPE` this writer emits a Blip Store entry for); any other format throws `BiffWriteError` naming it. Shape ids allocate workbook-wide from 1024 — Excel's own convention for a file's first drawing group — the invisible per-sheet "patriarch" root taking one id ahead of that sheet's own real shapes, so the Escher stream's shape order and the worksheet substream's `Obj` records pair up 1:1 exactly as the reader's own positional correlation expects.
131
+
132
+ A `ContentEmbeddedObject` writes through the identical Picture `Obj` machinery a plain image uses, but names an **Embedding Storage** instead of a Blip Store index: its `FtPictFmla` sub-record ([MS-XLS] 2.5.150) carries a storage id, and the outer [MS-CFB] compound file gets a sibling `MBD<8-hex-digit id>/Package` stream beside the `Workbook` stream ([MS-XLS] 2.1.7's own Embedding Storage naming convention) — `writeXlsContent` collects these from `buildDrawingWritePlan` and adds them to `writeCompoundFile`'s own stream list. What actually rides inside that Package stream (`src/workbook/embedded-object.ts`) is this package's own JSON serialisation of the embedded object's `objectKind`/`document`/`source` — the same honest boundary `rtf-codec`'s own embedded-object module draws for the identical reason: `xls-codec` cannot depend on `ooxml.js`/`odf.js` (format codecs are peers, never one another's dependency), so a `wordprocessing`/`presentation`/`spreadsheet`/`drawing`/`formula` object's own `ContentDocument` cannot be re-serialised into a real docx/pptx/xlsx/odf/MathML byte stream here the way a genuine OLE server would. `frame` and the anchor quartet are deliberately excluded from that payload: the Escher anchor (`ClientAnchorSheet`) is the one placement authority the format itself carries, both directions deriving every placement field from it, and a second, redundant copy inside the payload could only ever disagree with it. A `'chart'`-kind embedded object throws `BiffWriteError` by name rather than being approximated — see the [Writer scope](#writer-scope) table.
133
+
134
+ A record whose Escher bytes exceed the 8224-byte single-record ceiling — a sheet's own `MsoDrawing` record, or (once a workbook's images push its Blip Store past that size) the globals substream's `MsoDrawingGroup` record — chains onto `Continue` records via `writeRecordChain` rather than being refused, the one deliberate exception to the `Continue`-chain-splitting row the [Writer scope](#writer-scope) table states for every other record kind; reading a chained record back correctly required a corresponding fix on the read side, described below.
135
+
136
+ **A latent read-side bug, found and fixed while building this.** Verifying an image large enough to force a `Continue` chain surfaced a bug already on `main`, in code this feature did not touch: both `workbook/drawing.ts`'s `readSheetDrawing` (a sheet's own `MsoDrawing` bytes) and `content.ts`'s `concatDrawingGroupBytes` (the workbook-wide `MsoDrawingGroup` bytes) read only `record.blocks[0]` — the base record's own data — silently discarding every `Continue`-chained block beyond the first. Since a real picture's blip bytes routinely exceed one record's 8224-byte ceiling, this would have corrupted the Escher stream, and therefore the shape tree, for almost any real-world image, whether written by this package or by Excel itself. Both call sites now concatenate every block in the group, confirmed by a test that forces the chain and checks the image reads back byte-for-byte.
137
+
138
+ **A genuine write-side bug, found and fixed the same way.** The same large-image test also caught `OfficeArtClientAnchorSheet`'s own corner-cell offsets (`dxL`/`dyT`/`dxR`/`dyB`) being written as 4-byte fields when the record's own declared `recLen` (18 bytes total) and the reader's `readClientAnchor` both treat them as 2-byte fields — every field after the first offset landed at the wrong byte position, so any shape's placement beyond a trivial single-shape sheet came back with a wildly wrong size and anchor. Fixed to match the reader's own 2-byte reads. A second, independent bug in the same commit — `bytesFromBase64`'s own decode alphabet was missing `/`, one of the two non-alphanumeric characters standard base64 uses — meant any image whose base64 encoding happened to contain that character (which is most real images) silently dropped it, corrupting the written bytes. Both were caught by the same test, which decodes real base64 test fixtures large enough to statistically require a `/` and small enough to fit in one record, so it also stands as regression coverage for both defects independently of the `Continue`-chain case that surfaced them.
139
+
140
+ Verified by round trip (`src/write.test.ts`'s own `images and embedded objects` suite), entirely within this package: a single image's format, bytes, and cell anchor; an image large enough to force the workbook-wide Blip Store onto a `Continue` chain, confirmed by inspecting the raw record stream rather than trusting the round trip alone; many images on one sheet, forcing that sheet's own `MsoDrawing` record onto its own `Continue` chain; a non-chart embedded object through its own Embedding Storage; and the `'chart'` refusal. Not cross-checked against a real, independent BIFF8 implementation the way [Cell decoration](#cell-decoration)/[Cell alignment](#cell-alignment)/[Print settings](#print-settings) are — a genuinely useful follow-up, since every other decoration feature this package writes has that independent check and this one does not yet.
141
+
125
142
  ## Print settings
126
143
 
127
144
  Every field of `document-schema.js`'s own `ContentSheetPrintSettings` is read from and written to the BIFF8 records that carry it, verified both by round trip and against a real, independent BIFF8 implementation — [ExaDev/documents.js#815](https://github.com/ExaDev/documents.js/issues/815) as a scoped chunk of that issue's own broader tracking, not a claim of closing it outright. The two directions had to land together: before this, the reader returned Excel's fixed "Normal" preset for every sheet regardless of what the file said, so there was nothing to verify a writer's output against.
@@ -233,6 +250,7 @@ Layered bottom-up, each layer testable against hand-built byte sequences taken f
233
250
  - **[`excel-number-format`](../excel-number-format/README.md)**, **`src/serial.ts`** — number-format classification and date-serial conversion, the two pieces of xlsx semantics BIFF8 shares because ECMA-376 inherited them from BIFF. The classifier itself is a dependency shared with `ooxml.js`, not a module in this package (ExaDev/documents.js#848) — `classifyNumberFormat` and `BUILTIN_NUMBER_FORMATS` still ride this package's own barrel (`export * from "excel-number-format"` in `src/index.ts`), so `import { classifyNumberFormat } from "xls-codec"` is unchanged.
234
251
  - **`src/drawing/escher.ts`**, **`src/drawing/shapes.ts`**, **`src/drawing/blips.ts`** — MS-ODRAW (Escher), read as its own record tree independent of BIFF8's own record framing: the shared `OfficeArtRecordHeader` container/atom reader, the per-sheet shape walk (type, id, cell anchor, a picture's own `pib` blip reference), and the workbook-wide Blip Store a `pib` resolves against.
235
252
  - **`src/workbook/chart.ts`**, **`src/workbook/drawing.ts`** — an embedded chart's own nested substream, read into a flattened series/category table; and the orchestration that pairs each Escher shape with the `Obj` record naming what it holds (a picture, a chart, or a generic shape) and produces `ContentSheet.images`/`ContentSheet.embeddedObjects` — see "Charts, drawings, and images, resolved for real" under [Read-side gaps](#read-side-gaps).
253
+ - **`src/drawing/escher-writer.ts`**, **`src/drawing/md4.ts`**, **`src/workbook/drawing-writer.ts`**, **`src/workbook/embedded-object.ts`** — the write direction of the four modules above: the Escher container tree builder, a hand-written MD4 implementation for a Blip Store entry's own `rgbUid` hash, the workbook-wide drawing plan and `Obj`-record writers, and the Embedding Storage Package-stream wrapper an embedded object's own document rides inside — see [Images and embedded objects, written](#images-and-embedded-objects-written).
236
254
  - **`src/content.ts`** — the mapping onto `document-schema.js`.
237
255
  - **`src/metadata.ts`** — wraps `archive-codec`'s own `SummaryInformationProperties` <-> `LayoutMetadata` mapping with this package's `createdIso`/`modifiedIso` date validation, throwing `BiffWriteError` for a malformed one rather than letting an opaque `RangeError` escape the FILETIME conversion (see [Metadata](#metadata)).
238
256
 
@@ -13,6 +13,23 @@ function writeRecord(type, data) {
13
13
  out.set(data, HEADER_SIZE);
14
14
  return out;
15
15
  }
16
+ /**
17
+ * Splits `data` across a base record of `type` plus as many Continue records ([MS-XLS] 2.4.58) as it takes, each carrying up to MAX_RECORD_DATA_SIZE bytes -- the write-side mirror of biff/substreams.ts's groupRecords, which joins exactly this shape back together on read. `data` of MAX_RECORD_DATA_SIZE bytes or fewer still returns a single-element array (the base record alone), so a caller can always concatenate the result the same way regardless of whether a chain was actually needed.
18
+ *
19
+ * A Continue record's own data is the plain next slice of `data`, with no restated flag byte of its own: unlike an XLUnicodeRichExtendedString's fHighByte (biff/strings.ts's own concern), MsoDrawing/MsoDrawingGroup's Escher bytes carry no per-block header for a chain to restate, so simple concatenation IS the reassembly rule here -- confirmed against biff/substreams.ts's own groupRecords, which folds a plain Continue's block onto its base record verbatim rather than parsing anything out of it first.
20
+ */
21
+ function writeRecordChain(type, data) {
22
+ const records = [];
23
+ let offset = 0;
24
+ let recordType = type;
25
+ do {
26
+ const chunk = data.subarray(offset, offset + require_biff_record_types.MAX_RECORD_DATA_SIZE);
27
+ records.push(writeRecord(recordType, chunk));
28
+ offset += chunk.length;
29
+ recordType = 60;
30
+ } while (offset < data.length);
31
+ return records;
32
+ }
16
33
  /** Concatenates already-framed records (or any byte sequences) into one stream, in the order given. */
17
34
  function concatRecords(...parts) {
18
35
  const total = parts.reduce((sum, part) => sum + part.length, 0);
@@ -27,3 +44,4 @@ function concatRecords(...parts) {
27
44
  //#endregion
28
45
  exports.concatRecords = concatRecords;
29
46
  exports.writeRecord = writeRecord;
47
+ exports.writeRecordChain = writeRecordChain;
@@ -1,7 +1,13 @@
1
1
  //#region src/biff/record-writer.d.ts
2
2
  /** Wraps one record's data in its type/size header. Throws BiffWriteError if the data exceeds the single-record maximum, rather than splitting it across Continue records. */
3
3
  declare function writeRecord(type: number, data: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
4
+ /**
5
+ * Splits `data` across a base record of `type` plus as many Continue records ([MS-XLS] 2.4.58) as it takes, each carrying up to MAX_RECORD_DATA_SIZE bytes -- the write-side mirror of biff/substreams.ts's groupRecords, which joins exactly this shape back together on read. `data` of MAX_RECORD_DATA_SIZE bytes or fewer still returns a single-element array (the base record alone), so a caller can always concatenate the result the same way regardless of whether a chain was actually needed.
6
+ *
7
+ * A Continue record's own data is the plain next slice of `data`, with no restated flag byte of its own: unlike an XLUnicodeRichExtendedString's fHighByte (biff/strings.ts's own concern), MsoDrawing/MsoDrawingGroup's Escher bytes carry no per-block header for a chain to restate, so simple concatenation IS the reassembly rule here -- confirmed against biff/substreams.ts's own groupRecords, which folds a plain Continue's block onto its base record verbatim rather than parsing anything out of it first.
8
+ */
9
+ declare function writeRecordChain(type: number, data: Uint8Array<ArrayBuffer>): readonly Uint8Array<ArrayBuffer>[];
4
10
  /** Concatenates already-framed records (or any byte sequences) into one stream, in the order given. */
5
11
  declare function concatRecords(...parts: readonly Uint8Array<ArrayBuffer>[]): Uint8Array<ArrayBuffer>;
6
12
  //#endregion
7
- export { concatRecords, writeRecord };
13
+ export { concatRecords, writeRecord, writeRecordChain };
@@ -1,7 +1,13 @@
1
1
  //#region src/biff/record-writer.d.ts
2
2
  /** Wraps one record's data in its type/size header. Throws BiffWriteError if the data exceeds the single-record maximum, rather than splitting it across Continue records. */
3
3
  declare function writeRecord(type: number, data: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
4
+ /**
5
+ * Splits `data` across a base record of `type` plus as many Continue records ([MS-XLS] 2.4.58) as it takes, each carrying up to MAX_RECORD_DATA_SIZE bytes -- the write-side mirror of biff/substreams.ts's groupRecords, which joins exactly this shape back together on read. `data` of MAX_RECORD_DATA_SIZE bytes or fewer still returns a single-element array (the base record alone), so a caller can always concatenate the result the same way regardless of whether a chain was actually needed.
6
+ *
7
+ * A Continue record's own data is the plain next slice of `data`, with no restated flag byte of its own: unlike an XLUnicodeRichExtendedString's fHighByte (biff/strings.ts's own concern), MsoDrawing/MsoDrawingGroup's Escher bytes carry no per-block header for a chain to restate, so simple concatenation IS the reassembly rule here -- confirmed against biff/substreams.ts's own groupRecords, which folds a plain Continue's block onto its base record verbatim rather than parsing anything out of it first.
8
+ */
9
+ declare function writeRecordChain(type: number, data: Uint8Array<ArrayBuffer>): readonly Uint8Array<ArrayBuffer>[];
4
10
  /** Concatenates already-framed records (or any byte sequences) into one stream, in the order given. */
5
11
  declare function concatRecords(...parts: readonly Uint8Array<ArrayBuffer>[]): Uint8Array<ArrayBuffer>;
6
12
  //#endregion
7
- export { concatRecords, writeRecord };
13
+ export { concatRecords, writeRecord, writeRecordChain };
@@ -12,6 +12,23 @@ function writeRecord(type, data) {
12
12
  out.set(data, HEADER_SIZE);
13
13
  return out;
14
14
  }
15
+ /**
16
+ * Splits `data` across a base record of `type` plus as many Continue records ([MS-XLS] 2.4.58) as it takes, each carrying up to MAX_RECORD_DATA_SIZE bytes -- the write-side mirror of biff/substreams.ts's groupRecords, which joins exactly this shape back together on read. `data` of MAX_RECORD_DATA_SIZE bytes or fewer still returns a single-element array (the base record alone), so a caller can always concatenate the result the same way regardless of whether a chain was actually needed.
17
+ *
18
+ * A Continue record's own data is the plain next slice of `data`, with no restated flag byte of its own: unlike an XLUnicodeRichExtendedString's fHighByte (biff/strings.ts's own concern), MsoDrawing/MsoDrawingGroup's Escher bytes carry no per-block header for a chain to restate, so simple concatenation IS the reassembly rule here -- confirmed against biff/substreams.ts's own groupRecords, which folds a plain Continue's block onto its base record verbatim rather than parsing anything out of it first.
19
+ */
20
+ function writeRecordChain(type, data) {
21
+ const records = [];
22
+ let offset = 0;
23
+ let recordType = type;
24
+ do {
25
+ const chunk = data.subarray(offset, offset + MAX_RECORD_DATA_SIZE);
26
+ records.push(writeRecord(recordType, chunk));
27
+ offset += chunk.length;
28
+ recordType = 60;
29
+ } while (offset < data.length);
30
+ return records;
31
+ }
15
32
  /** Concatenates already-framed records (or any byte sequences) into one stream, in the order given. */
16
33
  function concatRecords(...parts) {
17
34
  const total = parts.reduce((sum, part) => sum + part.length, 0);
@@ -24,4 +41,4 @@ function concatRecords(...parts) {
24
41
  return out;
25
42
  }
26
43
  //#endregion
27
- export { concatRecords, writeRecord };
44
+ export { concatRecords, writeRecord, writeRecordChain };
@@ -8,6 +8,8 @@ const WORKBOOK_STREAM = "Workbook";
8
8
  const LEGACY_WORKBOOK_STREAM = "Book";
9
9
  /** The [MS-OLEPS] Property Set Stream a .xls's title/author/dates live in when present ([MS-OSHARED] 2.3.3.2.2) -- a genuinely optional stream, unlike Workbook, since a valid BIFF8 workbook need not carry document properties at all. */
10
10
  const SUMMARY_INFORMATION_STREAM = "SummaryInformation";
11
+ /** An MBD Embedding Storage's own directory name ([MS-XLS] 2.1.7): "MBD" followed by the eight-uppercase-hex-digit spelling of the storage id an embedded object's own FtPictFmla names -- workbook/drawing-writer.ts writes exactly this spelling, and a real producer's own embeddings use the identical convention. */
12
+ const EMBEDDING_STORAGE_PATTERN = /^MBD([0-9A-F]{8})\/Package$/;
11
13
  /**
12
14
  * Extracts the BIFF8 record stream, and the optional metadata stream beside it, from a .xls file's compound-file container.
13
15
  *
@@ -19,9 +21,15 @@ function readWorkbookStreams(bytes) {
19
21
  const workbook = streams.find((stream) => stream.path === WORKBOOK_STREAM);
20
22
  if (workbook !== void 0) {
21
23
  const metadata = streams.find((stream) => stream.path === SUMMARY_INFORMATION_STREAM);
24
+ const embeddingStreams = /* @__PURE__ */ new Map();
25
+ for (const stream of streams) {
26
+ const match = EMBEDDING_STORAGE_PATTERN.exec(stream.path);
27
+ if (match?.[1] !== void 0) embeddingStreams.set(Number.parseInt(match[1], 16), stream.bytes);
28
+ }
22
29
  return {
23
30
  workbook: workbook.bytes,
24
- metadata: metadata?.bytes
31
+ metadata: metadata?.bytes,
32
+ embeddingStreams
25
33
  };
26
34
  }
27
35
  if (streams.some((stream) => stream.path === LEGACY_WORKBOOK_STREAM)) throw new require_biff_records.BiffFormatError("compound file holds a 'Book' stream rather than a 'Workbook' stream, so it is a BIFF5/BIFF7 workbook; this reader implements BIFF8 only");
@@ -5,6 +5,8 @@ interface WorkbookStreams {
5
5
  readonly workbook: Uint8Array<ArrayBuffer>;
6
6
  /** The raw "\x05SummaryInformation" stream bytes, or undefined when the container carries none. */
7
7
  readonly metadata: Uint8Array<ArrayBuffer> | undefined;
8
+ /** Every "MBD<hex>/Package" Embedding Storage's own Package-stream bytes, keyed by the storage id its directory name spells -- an embedded object's own FtPictFmla names this same id (workbook/drawing.ts's own readEmbeddedObjectPackage resolves it). Empty when the workbook embeds no OLE object at all. */
9
+ readonly embeddingStreams: ReadonlyMap<number, Uint8Array<ArrayBuffer>>;
8
10
  }
9
11
  /**
10
12
  * Extracts the BIFF8 record stream, and the optional metadata stream beside it, from a .xls file's compound-file container.
@@ -5,6 +5,8 @@ interface WorkbookStreams {
5
5
  readonly workbook: Uint8Array<ArrayBuffer>;
6
6
  /** The raw "\x05SummaryInformation" stream bytes, or undefined when the container carries none. */
7
7
  readonly metadata: Uint8Array<ArrayBuffer> | undefined;
8
+ /** Every "MBD<hex>/Package" Embedding Storage's own Package-stream bytes, keyed by the storage id its directory name spells -- an embedded object's own FtPictFmla names this same id (workbook/drawing.ts's own readEmbeddedObjectPackage resolves it). Empty when the workbook embeds no OLE object at all. */
9
+ readonly embeddingStreams: ReadonlyMap<number, Uint8Array<ArrayBuffer>>;
8
10
  }
9
11
  /**
10
12
  * Extracts the BIFF8 record stream, and the optional metadata stream beside it, from a .xls file's compound-file container.
package/dist/container.js CHANGED
@@ -7,6 +7,8 @@ const WORKBOOK_STREAM = "Workbook";
7
7
  const LEGACY_WORKBOOK_STREAM = "Book";
8
8
  /** The [MS-OLEPS] Property Set Stream a .xls's title/author/dates live in when present ([MS-OSHARED] 2.3.3.2.2) -- a genuinely optional stream, unlike Workbook, since a valid BIFF8 workbook need not carry document properties at all. */
9
9
  const SUMMARY_INFORMATION_STREAM = "SummaryInformation";
10
+ /** An MBD Embedding Storage's own directory name ([MS-XLS] 2.1.7): "MBD" followed by the eight-uppercase-hex-digit spelling of the storage id an embedded object's own FtPictFmla names -- workbook/drawing-writer.ts writes exactly this spelling, and a real producer's own embeddings use the identical convention. */
11
+ const EMBEDDING_STORAGE_PATTERN = /^MBD([0-9A-F]{8})\/Package$/;
10
12
  /**
11
13
  * Extracts the BIFF8 record stream, and the optional metadata stream beside it, from a .xls file's compound-file container.
12
14
  *
@@ -18,9 +20,15 @@ function readWorkbookStreams(bytes) {
18
20
  const workbook = streams.find((stream) => stream.path === WORKBOOK_STREAM);
19
21
  if (workbook !== void 0) {
20
22
  const metadata = streams.find((stream) => stream.path === SUMMARY_INFORMATION_STREAM);
23
+ const embeddingStreams = /* @__PURE__ */ new Map();
24
+ for (const stream of streams) {
25
+ const match = EMBEDDING_STORAGE_PATTERN.exec(stream.path);
26
+ if (match?.[1] !== void 0) embeddingStreams.set(Number.parseInt(match[1], 16), stream.bytes);
27
+ }
21
28
  return {
22
29
  workbook: workbook.bytes,
23
- metadata: metadata?.bytes
30
+ metadata: metadata?.bytes,
31
+ embeddingStreams
24
32
  };
25
33
  }
26
34
  if (streams.some((stream) => stream.path === LEGACY_WORKBOOK_STREAM)) throw new BiffFormatError("compound file holds a 'Book' stream rather than a 'Workbook' stream, so it is a BIFF5/BIFF7 workbook; this reader implements BIFF8 only");
package/dist/content.cjs CHANGED
@@ -88,7 +88,7 @@ function mapPrintSettings(raw, names) {
88
88
  * The counterpart of ooxml.js's readXlsxContent, producing the same shape from the older format. `password` decrypts a workbook protected by [MS-XLS] 2.4.117's FilePass record, under either the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header scheme or 2.3.7's XOR obfuscation -- see workbook/encryption.ts. It is ignored for an unencrypted workbook, and a missing or incorrect password against an encrypted one throws rather than returning a partial or garbled document.
89
89
  */
90
90
  function readXlsContent(bytes, password) {
91
- const { workbook, metadata } = require_container.readWorkbookStreams(bytes);
91
+ const { workbook, metadata, embeddingStreams } = require_container.readWorkbookStreams(bytes);
92
92
  const rawRecords = require_biff_records.readRecords(workbook);
93
93
  const filePassRecord = rawRecords.find((record) => record.type === 47);
94
94
  const records = filePassRecord === void 0 ? rawRecords : require_workbook_encryption.decryptWorkbookRecords(rawRecords, filePassRecord, password);
@@ -105,7 +105,7 @@ function readXlsContent(bytes, password) {
105
105
  return {
106
106
  kind: "spreadsheet",
107
107
  metadata: documentMetadata,
108
- sheets: worksheetEntries.map(({ entry, sheetIndex }) => readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata)),
108
+ sheets: worksheetEntries.map(({ entry, sheetIndex }) => readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata, embeddingStreams)),
109
109
  ...mapDefinedNames(globalsSubstream.records, globals, worksheetEntries)
110
110
  };
111
111
  }
@@ -140,7 +140,7 @@ function mapDefinedNames(globalsRecords, globals, worksheetEntries) {
140
140
  }
141
141
  /** Every MsoDrawingGroup record's own data, in stream order, concatenated into one Escher byte stream -- the workbook-wide counterpart of a worksheet's own MsoDrawing concatenation (drawing/shapes.ts's own readSheetShapes), carrying the Blip Store rather than any one sheet's shape tree. */
142
142
  function concatDrawingGroupBytes(records) {
143
- const chunks = records.filter((record) => record.type === 235).map((record) => record.blocks[0]).filter((block) => block !== void 0);
143
+ const chunks = records.filter((record) => record.type === 235).flatMap((record) => record.blocks);
144
144
  return require_drawing_bytes.concatBytes(chunks);
145
145
  }
146
146
  /** The tree-form read: readXlsContent composed with the schema's own structural transform, exactly as ooxml.js's readXlsx wraps readXlsxContent. */
@@ -152,7 +152,7 @@ function readXls(bytes, password) {
152
152
  *
153
153
  * The substream is found by the byte offset BoundSheet8's lbPlyPos names, not by position: the order sheets appear in the workbook (which is BoundSheet8 order, and therefore the order of `globals.sheets`) is not required to match the order their substreams were written in. A sheet whose substream cannot be found still produces a ContentSheet, empty -- losing the sheet entirely would be a worse answer than losing its cells, since its name and position are real information the workbook did state.
154
154
  */
155
- function readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata) {
155
+ function readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata, embeddingStreams) {
156
156
  const substream = substreams.find((candidate) => candidate.offset === entry.bofPosition && candidate.documentType === 16);
157
157
  const emptyPrint = {
158
158
  marginsPt: {},
@@ -186,7 +186,8 @@ function readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMe
186
186
  formulaSheets,
187
187
  ownSheetCells: cells,
188
188
  metadata: documentMetadata,
189
- allSubstreams: substreams
189
+ allSubstreams: substreams,
190
+ embeddingStreams
190
191
  });
191
192
  return {
192
193
  name: entry.name,
package/dist/content.js CHANGED
@@ -87,7 +87,7 @@ function mapPrintSettings(raw, names) {
87
87
  * The counterpart of ooxml.js's readXlsxContent, producing the same shape from the older format. `password` decrypts a workbook protected by [MS-XLS] 2.4.117's FilePass record, under either the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header scheme or 2.3.7's XOR obfuscation -- see workbook/encryption.ts. It is ignored for an unencrypted workbook, and a missing or incorrect password against an encrypted one throws rather than returning a partial or garbled document.
88
88
  */
89
89
  function readXlsContent(bytes, password) {
90
- const { workbook, metadata } = readWorkbookStreams(bytes);
90
+ const { workbook, metadata, embeddingStreams } = readWorkbookStreams(bytes);
91
91
  const rawRecords = readRecords(workbook);
92
92
  const filePassRecord = rawRecords.find((record) => record.type === 47);
93
93
  const records = filePassRecord === void 0 ? rawRecords : decryptWorkbookRecords(rawRecords, filePassRecord, password);
@@ -104,7 +104,7 @@ function readXlsContent(bytes, password) {
104
104
  return {
105
105
  kind: "spreadsheet",
106
106
  metadata: documentMetadata,
107
- sheets: worksheetEntries.map(({ entry, sheetIndex }) => readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata)),
107
+ sheets: worksheetEntries.map(({ entry, sheetIndex }) => readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata, embeddingStreams)),
108
108
  ...mapDefinedNames(globalsSubstream.records, globals, worksheetEntries)
109
109
  };
110
110
  }
@@ -139,7 +139,7 @@ function mapDefinedNames(globalsRecords, globals, worksheetEntries) {
139
139
  }
140
140
  /** Every MsoDrawingGroup record's own data, in stream order, concatenated into one Escher byte stream -- the workbook-wide counterpart of a worksheet's own MsoDrawing concatenation (drawing/shapes.ts's own readSheetShapes), carrying the Blip Store rather than any one sheet's shape tree. */
141
141
  function concatDrawingGroupBytes(records) {
142
- const chunks = records.filter((record) => record.type === 235).map((record) => record.blocks[0]).filter((block) => block !== void 0);
142
+ const chunks = records.filter((record) => record.type === 235).flatMap((record) => record.blocks);
143
143
  return concatBytes(chunks);
144
144
  }
145
145
  /** The tree-form read: readXlsContent composed with the schema's own structural transform, exactly as ooxml.js's readXlsx wraps readXlsxContent. */
@@ -151,7 +151,7 @@ function readXls(bytes, password) {
151
151
  *
152
152
  * The substream is found by the byte offset BoundSheet8's lbPlyPos names, not by position: the order sheets appear in the workbook (which is BoundSheet8 order, and therefore the order of `globals.sheets`) is not required to match the order their substreams were written in. A sheet whose substream cannot be found still produces a ContentSheet, empty -- losing the sheet entirely would be a worse answer than losing its cells, since its name and position are real information the workbook did state.
153
153
  */
154
- function readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata) {
154
+ function readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMetadata, embeddingStreams) {
155
155
  const substream = substreams.find((candidate) => candidate.offset === entry.bofPosition && candidate.documentType === 16);
156
156
  const emptyPrint = {
157
157
  marginsPt: {},
@@ -185,7 +185,8 @@ function readSheet(entry, sheetIndex, substreams, globals, blipStore, documentMe
185
185
  formulaSheets,
186
186
  ownSheetCells: cells,
187
187
  metadata: documentMetadata,
188
- allSubstreams: substreams
188
+ allSubstreams: substreams,
189
+ embeddingStreams
189
190
  });
190
191
  return {
191
192
  name: entry.name,
@@ -16,6 +16,10 @@ const ESCHER_BSE = 61447;
16
16
  const ESCHER_SP = 61450;
17
17
  /** A shape's own property table (fopt): a run of fixed-size property entries this reader consults for exactly one property, `pib` -- see FOPT_PROPERTY_PIB below ([MS-ODRAW] OfficeArtFOPT, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/10dc2fe1-9e69-48dc-a1d1-2921dfb9c28e). */
18
18
  const ESCHER_OPT = 61451;
19
+ /** A group shape's own coordinate system ([MS-ODRAW] OfficeArtFSPGR, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/82d2d6a1-3a7a-4d15-9803-33145a76545a): four i32 coordinates, written as the all-zero rectangle a patriarch group whose children are all anchored in the sheet's own cell grid states. */
20
+ const ESCHER_FSPGR = 61449;
21
+ /** The marker every shape whose following BIFF record is an Obj carries: [MS-XLS]'s own MsoDrawing prose ("If the rgChildRec has a shape structure in it ... and that shape has a clientData record in it ..., then the next record following this record MUST be an Obj") makes this empty atom the join between one Escher shape and its Obj record. [MS-ODRAW] OfficeArtClientData. */
22
+ const ESCHER_CLIENT_DATA = 61457;
19
23
  /** A shape's cell-anchor placement in a worksheet, macro sheet, or dialog sheet substream -- [MS-ODRAW]'s own generic ClientAnchor atom carries a host-defined payload, and [MS-XLS] 2.5.163 OfficeArtClientAnchorSheet (https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/fd656a2c-d5ee-4171-8f65-17a08b9f2262) is what that payload actually is here (a chart-sheet substream uses the distinct OfficeArtClientAnchorChart instead, not read by this package). */
20
24
  const ESCHER_CLIENT_ANCHOR = 61456;
21
25
  const ESCHER_BLIP_JPEG_A = 61469;
@@ -39,8 +43,10 @@ exports.ESCHER_BLIP_PNG = ESCHER_BLIP_PNG;
39
43
  exports.ESCHER_BSE = ESCHER_BSE;
40
44
  exports.ESCHER_BSTORE_CONTAINER = ESCHER_BSTORE_CONTAINER;
41
45
  exports.ESCHER_CLIENT_ANCHOR = ESCHER_CLIENT_ANCHOR;
46
+ exports.ESCHER_CLIENT_DATA = ESCHER_CLIENT_DATA;
42
47
  exports.ESCHER_DGG_CONTAINER = ESCHER_DGG_CONTAINER;
43
48
  exports.ESCHER_DG_CONTAINER = ESCHER_DG_CONTAINER;
49
+ exports.ESCHER_FSPGR = ESCHER_FSPGR;
44
50
  exports.ESCHER_OPT = ESCHER_OPT;
45
51
  exports.ESCHER_SP = ESCHER_SP;
46
52
  exports.ESCHER_SPGR_CONTAINER = ESCHER_SPGR_CONTAINER;
@@ -15,6 +15,10 @@ declare const ESCHER_BSE = 61447;
15
15
  declare const ESCHER_SP = 61450;
16
16
  /** A shape's own property table (fopt): a run of fixed-size property entries this reader consults for exactly one property, `pib` -- see FOPT_PROPERTY_PIB below ([MS-ODRAW] OfficeArtFOPT, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/10dc2fe1-9e69-48dc-a1d1-2921dfb9c28e). */
17
17
  declare const ESCHER_OPT = 61451;
18
+ /** A group shape's own coordinate system ([MS-ODRAW] OfficeArtFSPGR, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/82d2d6a1-3a7a-4d15-9803-33145a76545a): four i32 coordinates, written as the all-zero rectangle a patriarch group whose children are all anchored in the sheet's own cell grid states. */
19
+ declare const ESCHER_FSPGR = 61449;
20
+ /** The marker every shape whose following BIFF record is an Obj carries: [MS-XLS]'s own MsoDrawing prose ("If the rgChildRec has a shape structure in it ... and that shape has a clientData record in it ..., then the next record following this record MUST be an Obj") makes this empty atom the join between one Escher shape and its Obj record. [MS-ODRAW] OfficeArtClientData. */
21
+ declare const ESCHER_CLIENT_DATA = 61457;
18
22
  /** A shape's cell-anchor placement in a worksheet, macro sheet, or dialog sheet substream -- [MS-ODRAW]'s own generic ClientAnchor atom carries a host-defined payload, and [MS-XLS] 2.5.163 OfficeArtClientAnchorSheet (https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/fd656a2c-d5ee-4171-8f65-17a08b9f2262) is what that payload actually is here (a chart-sheet substream uses the distinct OfficeArtClientAnchorChart instead, not read by this package). */
19
23
  declare const ESCHER_CLIENT_ANCHOR = 61456;
20
24
  declare const ESCHER_BLIP_JPEG_A = 61469;
@@ -31,4 +35,4 @@ declare const SHAPE_TYPE_TEXT_BOX = 202;
31
35
  declare const FOPT_OPID_PIB = 260;
32
36
  declare const FOPT_FCOMPLEX_MASK = 32768;
33
37
  //#endregion
34
- export { ESCHER_BLIP_DIB, ESCHER_BLIP_JPEG_A, ESCHER_BLIP_JPEG_B, ESCHER_BLIP_PNG, ESCHER_BSE, ESCHER_BSTORE_CONTAINER, ESCHER_CLIENT_ANCHOR, ESCHER_DGG_CONTAINER, ESCHER_DG_CONTAINER, ESCHER_OPT, ESCHER_SP, ESCHER_SPGR_CONTAINER, ESCHER_SP_CONTAINER, FOPT_FCOMPLEX_MASK, FOPT_OPID_PIB, SHAPE_TYPE_NOT_PRIMITIVE, SHAPE_TYPE_PICTURE_FRAME, SHAPE_TYPE_TEXT_BOX };
38
+ export { ESCHER_BLIP_DIB, ESCHER_BLIP_JPEG_A, ESCHER_BLIP_JPEG_B, ESCHER_BLIP_PNG, ESCHER_BSE, ESCHER_BSTORE_CONTAINER, ESCHER_CLIENT_ANCHOR, ESCHER_CLIENT_DATA, ESCHER_DGG_CONTAINER, ESCHER_DG_CONTAINER, ESCHER_FSPGR, ESCHER_OPT, ESCHER_SP, ESCHER_SPGR_CONTAINER, ESCHER_SP_CONTAINER, FOPT_FCOMPLEX_MASK, FOPT_OPID_PIB, SHAPE_TYPE_NOT_PRIMITIVE, SHAPE_TYPE_PICTURE_FRAME, SHAPE_TYPE_TEXT_BOX };
@@ -15,6 +15,10 @@ declare const ESCHER_BSE = 61447;
15
15
  declare const ESCHER_SP = 61450;
16
16
  /** A shape's own property table (fopt): a run of fixed-size property entries this reader consults for exactly one property, `pib` -- see FOPT_PROPERTY_PIB below ([MS-ODRAW] OfficeArtFOPT, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/10dc2fe1-9e69-48dc-a1d1-2921dfb9c28e). */
17
17
  declare const ESCHER_OPT = 61451;
18
+ /** A group shape's own coordinate system ([MS-ODRAW] OfficeArtFSPGR, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/82d2d6a1-3a7a-4d15-9803-33145a76545a): four i32 coordinates, written as the all-zero rectangle a patriarch group whose children are all anchored in the sheet's own cell grid states. */
19
+ declare const ESCHER_FSPGR = 61449;
20
+ /** The marker every shape whose following BIFF record is an Obj carries: [MS-XLS]'s own MsoDrawing prose ("If the rgChildRec has a shape structure in it ... and that shape has a clientData record in it ..., then the next record following this record MUST be an Obj") makes this empty atom the join between one Escher shape and its Obj record. [MS-ODRAW] OfficeArtClientData. */
21
+ declare const ESCHER_CLIENT_DATA = 61457;
18
22
  /** A shape's cell-anchor placement in a worksheet, macro sheet, or dialog sheet substream -- [MS-ODRAW]'s own generic ClientAnchor atom carries a host-defined payload, and [MS-XLS] 2.5.163 OfficeArtClientAnchorSheet (https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/fd656a2c-d5ee-4171-8f65-17a08b9f2262) is what that payload actually is here (a chart-sheet substream uses the distinct OfficeArtClientAnchorChart instead, not read by this package). */
19
23
  declare const ESCHER_CLIENT_ANCHOR = 61456;
20
24
  declare const ESCHER_BLIP_JPEG_A = 61469;
@@ -31,4 +35,4 @@ declare const SHAPE_TYPE_TEXT_BOX = 202;
31
35
  declare const FOPT_OPID_PIB = 260;
32
36
  declare const FOPT_FCOMPLEX_MASK = 32768;
33
37
  //#endregion
34
- export { ESCHER_BLIP_DIB, ESCHER_BLIP_JPEG_A, ESCHER_BLIP_JPEG_B, ESCHER_BLIP_PNG, ESCHER_BSE, ESCHER_BSTORE_CONTAINER, ESCHER_CLIENT_ANCHOR, ESCHER_DGG_CONTAINER, ESCHER_DG_CONTAINER, ESCHER_OPT, ESCHER_SP, ESCHER_SPGR_CONTAINER, ESCHER_SP_CONTAINER, FOPT_FCOMPLEX_MASK, FOPT_OPID_PIB, SHAPE_TYPE_NOT_PRIMITIVE, SHAPE_TYPE_PICTURE_FRAME, SHAPE_TYPE_TEXT_BOX };
38
+ export { ESCHER_BLIP_DIB, ESCHER_BLIP_JPEG_A, ESCHER_BLIP_JPEG_B, ESCHER_BLIP_PNG, ESCHER_BSE, ESCHER_BSTORE_CONTAINER, ESCHER_CLIENT_ANCHOR, ESCHER_CLIENT_DATA, ESCHER_DGG_CONTAINER, ESCHER_DG_CONTAINER, ESCHER_FSPGR, ESCHER_OPT, ESCHER_SP, ESCHER_SPGR_CONTAINER, ESCHER_SP_CONTAINER, FOPT_FCOMPLEX_MASK, FOPT_OPID_PIB, SHAPE_TYPE_NOT_PRIMITIVE, SHAPE_TYPE_PICTURE_FRAME, SHAPE_TYPE_TEXT_BOX };
@@ -15,6 +15,10 @@ const ESCHER_BSE = 61447;
15
15
  const ESCHER_SP = 61450;
16
16
  /** A shape's own property table (fopt): a run of fixed-size property entries this reader consults for exactly one property, `pib` -- see FOPT_PROPERTY_PIB below ([MS-ODRAW] OfficeArtFOPT, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/10dc2fe1-9e69-48dc-a1d1-2921dfb9c28e). */
17
17
  const ESCHER_OPT = 61451;
18
+ /** A group shape's own coordinate system ([MS-ODRAW] OfficeArtFSPGR, https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/82d2d6a1-3a7a-4d15-9803-33145a76545a): four i32 coordinates, written as the all-zero rectangle a patriarch group whose children are all anchored in the sheet's own cell grid states. */
19
+ const ESCHER_FSPGR = 61449;
20
+ /** The marker every shape whose following BIFF record is an Obj carries: [MS-XLS]'s own MsoDrawing prose ("If the rgChildRec has a shape structure in it ... and that shape has a clientData record in it ..., then the next record following this record MUST be an Obj") makes this empty atom the join between one Escher shape and its Obj record. [MS-ODRAW] OfficeArtClientData. */
21
+ const ESCHER_CLIENT_DATA = 61457;
18
22
  /** A shape's cell-anchor placement in a worksheet, macro sheet, or dialog sheet substream -- [MS-ODRAW]'s own generic ClientAnchor atom carries a host-defined payload, and [MS-XLS] 2.5.163 OfficeArtClientAnchorSheet (https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/fd656a2c-d5ee-4171-8f65-17a08b9f2262) is what that payload actually is here (a chart-sheet substream uses the distinct OfficeArtClientAnchorChart instead, not read by this package). */
19
23
  const ESCHER_CLIENT_ANCHOR = 61456;
20
24
  const ESCHER_BLIP_JPEG_A = 61469;
@@ -31,4 +35,4 @@ const SHAPE_TYPE_TEXT_BOX = 202;
31
35
  const FOPT_OPID_PIB = 260;
32
36
  const FOPT_FCOMPLEX_MASK = 32768;
33
37
  //#endregion
34
- export { ESCHER_BLIP_DIB, ESCHER_BLIP_JPEG_A, ESCHER_BLIP_JPEG_B, ESCHER_BLIP_PNG, ESCHER_BSE, ESCHER_BSTORE_CONTAINER, ESCHER_CLIENT_ANCHOR, ESCHER_DGG_CONTAINER, ESCHER_DG_CONTAINER, ESCHER_OPT, ESCHER_SP, ESCHER_SPGR_CONTAINER, ESCHER_SP_CONTAINER, FOPT_FCOMPLEX_MASK, FOPT_OPID_PIB, SHAPE_TYPE_NOT_PRIMITIVE, SHAPE_TYPE_PICTURE_FRAME, SHAPE_TYPE_TEXT_BOX };
38
+ export { ESCHER_BLIP_DIB, ESCHER_BLIP_JPEG_A, ESCHER_BLIP_JPEG_B, ESCHER_BLIP_PNG, ESCHER_BSE, ESCHER_BSTORE_CONTAINER, ESCHER_CLIENT_ANCHOR, ESCHER_CLIENT_DATA, ESCHER_DGG_CONTAINER, ESCHER_DG_CONTAINER, ESCHER_FSPGR, ESCHER_OPT, ESCHER_SP, ESCHER_SPGR_CONTAINER, ESCHER_SP_CONTAINER, FOPT_FCOMPLEX_MASK, FOPT_OPID_PIB, SHAPE_TYPE_NOT_PRIMITIVE, SHAPE_TYPE_PICTURE_FRAME, SHAPE_TYPE_TEXT_BOX };