reamkit 1.14.0 → 1.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 (62) hide show
  1. package/README.md +34 -8
  2. package/dist/esm/core/converter/facade.d.ts +8 -1
  3. package/dist/esm/core/converter/facade.js +34 -8
  4. package/dist/esm/core/converter/ream.d.ts +1 -0
  5. package/dist/esm/core/converter/ream.js +13 -8
  6. package/dist/esm/core/ir/features.d.ts +1 -0
  7. package/dist/esm/core/ir/features.js +1 -0
  8. package/dist/esm/core/ir/sheet.d.ts +7 -0
  9. package/dist/esm/core/ole/cfb.d.ts +19 -0
  10. package/dist/esm/core/ole/cfb.js +158 -0
  11. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  12. package/dist/esm/core/spreadsheet-model/types.d.ts +20 -1
  13. package/dist/esm/excel/activex-parser.d.ts +9 -0
  14. package/dist/esm/excel/activex-parser.js +188 -0
  15. package/dist/esm/excel/conditional-format.d.ts +4 -2
  16. package/dist/esm/excel/conditional-format.js +138 -4
  17. package/dist/esm/excel/formula/context.d.ts +11 -0
  18. package/dist/esm/excel/formula/dates.d.ts +11 -0
  19. package/dist/esm/excel/formula/dates.js +46 -0
  20. package/dist/esm/excel/formula/eval.d.ts +11 -0
  21. package/dist/esm/excel/formula/eval.js +196 -0
  22. package/dist/esm/excel/formula/functions.d.ts +7 -0
  23. package/dist/esm/excel/formula/functions.js +1359 -0
  24. package/dist/esm/excel/formula/index.d.ts +14 -0
  25. package/dist/esm/excel/formula/index.js +17 -0
  26. package/dist/esm/excel/formula/lexer.d.ts +8 -0
  27. package/dist/esm/excel/formula/lexer.js +163 -0
  28. package/dist/esm/excel/formula/parser.d.ts +57 -0
  29. package/dist/esm/excel/formula/parser.js +274 -0
  30. package/dist/esm/excel/formula/value.d.ts +52 -0
  31. package/dist/esm/excel/formula/value.js +102 -0
  32. package/dist/esm/excel/number-format.d.ts +2 -0
  33. package/dist/esm/excel/number-format.js +5 -1
  34. package/dist/esm/excel/print-model.d.ts +3 -0
  35. package/dist/esm/excel/print-model.js +1 -1
  36. package/dist/esm/excel/sheet-to-flow.d.ts +4 -1
  37. package/dist/esm/excel/sheet-to-flow.js +46 -2
  38. package/dist/esm/excel/worksheet-parser.js +66 -0
  39. package/dist/esm/excel/xls/biff-chart.d.ts +8 -0
  40. package/dist/esm/excel/xls/biff-chart.js +121 -0
  41. package/dist/esm/excel/xls/biff-reader.d.ts +4 -0
  42. package/dist/esm/excel/xls/biff-reader.js +1418 -0
  43. package/dist/esm/excel/xls/biff-styles.d.ts +8 -0
  44. package/dist/esm/excel/xls/biff-styles.js +299 -0
  45. package/dist/esm/excel/xls/escher.d.ts +24 -0
  46. package/dist/esm/excel/xls/escher.js +171 -0
  47. package/dist/esm/excel/xls/xls-reader.d.ts +3 -0
  48. package/dist/esm/excel/xls/xls-reader.js +30 -0
  49. package/dist/esm/excel/xlsx-reader.d.ts +2 -1
  50. package/dist/esm/excel/xlsx-reader.js +28 -3
  51. package/dist/esm/excel/xlsx-to-pdf.d.ts +1 -0
  52. package/dist/esm/excel/xlsx-to-pdf.js +2 -2
  53. package/dist/esm/excel/xlsx-writer.js +2 -0
  54. package/dist/esm/pptx/ppt/ppt-reader.d.ts +4 -0
  55. package/dist/esm/pptx/ppt/ppt-reader.js +337 -0
  56. package/dist/esm/pptx/ppt/ppt-text.d.ts +69 -0
  57. package/dist/esm/pptx/ppt/ppt-text.js +756 -0
  58. package/dist/esm/word/doc/doc-reader.d.ts +4 -0
  59. package/dist/esm/word/doc/doc-reader.js +421 -0
  60. package/dist/esm/word/doc/doc-text.d.ts +79 -0
  61. package/dist/esm/word/doc/doc-text.js +706 -0
  62. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,20 @@
1
1
  # Ream
2
2
 
3
- > DOCX & XLSX → PDF, from scratch — no LibreOffice, no headless Office, no commercial SDK.
4
-
5
- Convert Word (`.docx`) and Excel (`.xlsx`) documents to PDF, **in the browser** —
6
- implemented from the **ECMA-376** (OOXML) and **ISO 32000** (PDF) specifications,
7
- with no wrapper around LibreOffice, headless Office, or any commercial SDK. Pure
8
- TypeScript/JavaScript working on `Uint8Array` in and `Uint8Array` out, so it also
9
- runs unchanged in Node.js, serverless, and edge runtimes.
3
+ > Read Word, Excel, PowerPoint and PDF — and convert any of them to PDF, SVG, HTML, DOCX or XLSX. From scratch, in the browser. No LibreOffice, no headless Office, no commercial SDK.
4
+
5
+ Ream parses **seven document formats** — the modern Office Open XML trio (`.docx`,
6
+ `.xlsx`, `.pptx`), `.pdf`, and the legacy binary `.doc` / `.xls` / `.ppt` — into one
7
+ format-neutral interlayer, then renders that to **PDF, SVG, HTML, DOCX or XLSX**.
8
+ It is implemented directly from the **ECMA-376** (OOXML), **ISO 32000 / 19005**
9
+ (PDF / PDF/A), and Microsoft's binary-format specifications — no wrapper around
10
+ LibreOffice, headless Office, or any commercial SDK. Pure TypeScript/JavaScript on
11
+ `Uint8Array` in and `Uint8Array` out, so the same code runs unchanged in the browser,
12
+ Node.js, serverless and edge runtimes.
13
+
14
+ | | |
15
+ | ---------- | ------------------------------------------------------------------------------------------- |
16
+ | **Reads** | `.docx` · `.xlsx` · `.pptx` · `.pdf` · legacy `.doc` · `.xls` · `.ppt` |
17
+ | **Writes** | `.pdf` (incl. PDF/A-1/2/3, PDF/UA-1, signed, encrypted) · `.svg` · `.html` · `.docx` · `.xlsx` |
10
18
 
11
19
  ## Install
12
20
 
@@ -168,7 +176,8 @@ fields)/footnotes and endnotes/hyperlinks and bookmarks/floating drawings/
168
176
  images/tracked changes, SpreadsheetML grids,
169
177
  number formats and the print model (gridlines, print area, fit-to-page,
170
178
  repeated titles, page breaks), **conditional formatting** (color scales, data
171
- bars, icon sets), **sparklines** and **Excel tables**, DrawingML shapes and
179
+ bars, icon sets, and `expression` rules evaluated by a ~140-function formula
180
+ engine), **sparklines** and **Excel tables**, DrawingML shapes and
172
181
  charts, OMML math, Type0+CIDFontType2 embedding with subsetting, Knuth-Plass
173
182
  line breaking, Liang hyphenation, OpenType ligatures/kerning + Arabic cursive
174
183
  joining, BiDi (UAX #9), hyperlinks (PDF link annotations + HTML anchors,
@@ -193,6 +202,23 @@ bullets and indents), layout/master placeholders, pictures, shapes, DrawingML
193
202
  tables, embedded charts, theme colours, slide backgrounds, grouped shapes and
194
203
  hyperlinks — then converts onward to PDF, SVG, HTML or DOCX like any source.
195
204
 
205
+ **Reads legacy `.doc`, `.xls` and `.ppt`, too.** The binary Word / Excel /
206
+ PowerPoint 97–2003 formats (OLE2/CFB) parse through a shared container reader: a
207
+ `.doc` yields its text with run and paragraph formatting, tables (with cell
208
+ borders, vertical merges and background shading), inline images, fields,
209
+ headers/footers and lists (numbered or bulleted, in their number format);
210
+ an `.xls` yields the grid with styling, embedded
211
+ images, charts, drawing shapes, cell hyperlinks, the page-setup print model,
212
+ defined names (named ranges, print area, repeated titles), cell comments, data
213
+ validation, frozen panes, custom row heights and conditional formatting (the
214
+ classic cellIs / expression rules **and** the 2007 colour-scale / data-bar /
215
+ icon-set extensions); a `.ppt` yields each slide's
216
+ text (with run and paragraph formatting), embedded images, per-shape placement
217
+ (anchored text boxes and pictures at their slide rectangles) and decorative
218
+ autoshapes (preset or exact freeform geometry, with fill / line colours resolved
219
+ through the slide's colour scheme), one page per slide — all convert onward to PDF,
220
+ SVG, HTML, or back to `.docx` / `.xlsx` like any source.
221
+
196
222
  See [`CHANGELOG.md`](./CHANGELOG.md) for the release history; the docs
197
223
  [**Scope**](https://reamkit.dev/guides/scope/) guide has the full feature matrix
198
224
  and known limitations.
@@ -5,6 +5,7 @@ import { SheetDoc } from '../ir/sheet.js';
5
5
  import { FontProvider } from '../fonts/provider.js';
6
6
  import { Loss, LossReport } from '../ir/index.js';
7
7
  import { FontBytesByVariant } from '../font/index.js';
8
+ import { ProjectSheetOptions } from '../../excel/sheet-to-flow.js';
8
9
  export interface ConvertOptions extends ConvertDocxOptions {
9
10
  /**
10
11
  * Target: 'pdf' (default), 'svg' (page-stack preview), 'html'/'docx' (flowed),
@@ -16,6 +17,12 @@ export interface ConvertOptions extends ConvertDocxOptions {
16
17
  * recorded loss instead of returning it in the report.
17
18
  */
18
19
  readonly strict?: boolean;
20
+ /**
21
+ * E-SHEET W9 — reference date for conditional-format `timePeriod` rules and
22
+ * TODAY()/NOW() in `expression` rules (spreadsheet input). An explicit input,
23
+ * never the wall clock; omitted ⇒ those clock-relative rules no-op.
24
+ */
25
+ readonly now?: Date;
19
26
  /**
20
27
  * Font resolution chain (ir-design §8). When set (and no caller `fonts`),
21
28
  * the facade resolves the default font set through these providers — e.g.
@@ -31,7 +38,7 @@ export interface ConvertResult {
31
38
  readonly losses: LossReport;
32
39
  }
33
40
  export type SourceDoc = FlowDoc | SheetDoc;
34
- export declare function toFlowDoc(doc: SourceDoc): FlowDoc;
41
+ export declare function toFlowDoc(doc: SourceDoc, options?: ProjectSheetOptions): FlowDoc;
35
42
  export interface Converter {
36
43
  /** The registered readers, in sniffing order. */
37
44
  readonly readers: ReadonlyArray<DocumentReader<SourceDoc>>;
@@ -1,7 +1,10 @@
1
+ import { fetchFontSet } from "../fonts/remote-fonts.js";
1
2
  import { FontRegistry } from "../font/font-registry.js";
2
3
  import { FEATURES } from "../ir/features.js";
3
4
  import { ConversionLossError } from "../ir/loss.js";
4
5
  import { layoutStyledDocument } from "../../layout/styled-layout.js";
6
+ import { renderStyledPdf } from "../../pdf/styled-page-renderer.js";
7
+ import "../../pdf/index.js";
5
8
  import { pptxReader } from "../../pptx/pptx-reader.js";
6
9
  import { docxReader } from "../../word/docx-reader.js";
7
10
  import { projectSheetDoc } from "../../excel/sheet-to-flow.js";
@@ -14,22 +17,28 @@ import { writeHtml } from "../../html/html-writer.js";
14
17
  import { writeSvg } from "../../svg/svg-writer.js";
15
18
  import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
16
19
  import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
20
+ import { docReader } from "../../word/doc/doc-reader.js";
21
+ import { xlsReader } from "../../excel/xls/xls-reader.js";
22
+ import { pptReader } from "../../pptx/ppt/ppt-reader.js";
17
23
  import { pdfReader } from "../../pdf-reader/reader.js";
18
24
  //#region src/core/converter/facade.ts
19
- function toFlowDoc(doc) {
20
- return doc.kind === "sheet" ? projectSheetDoc(doc) : doc;
25
+ function toFlowDoc(doc, options = {}) {
26
+ return doc.kind === "sheet" ? projectSheetDoc(doc, options) : doc;
21
27
  }
22
- function readToFlow(reader, bytes) {
28
+ function readToFlow(reader, bytes, options = {}) {
23
29
  const { doc, losses } = reader.read(bytes);
24
30
  return {
25
- doc: toFlowDoc(doc),
31
+ doc: toFlowDoc(doc, options),
26
32
  losses
27
33
  };
28
34
  }
29
35
  var DEFAULT_READERS = [
30
36
  docxReader,
37
+ docReader,
31
38
  xlsxReader,
39
+ xlsReader,
32
40
  pptxReader,
41
+ pptReader,
33
42
  pdfReader
34
43
  ];
35
44
  function createConverter(opts = {}) {
@@ -41,7 +50,7 @@ function createConverter(opts = {}) {
41
50
  if (!reader) throw new Error("Unrecognized input format (no registered reader sniffs these bytes)");
42
51
  const losses = [];
43
52
  if (to === "docx") {
44
- const { doc: flow, losses: readLosses } = readToFlow(reader, bytes);
53
+ const { doc: flow, losses: readLosses } = readToFlow(reader, bytes, rest.now ? { now: rest.now } : void 0);
45
54
  losses.push(...readLosses);
46
55
  const out = writeDocx(flow);
47
56
  losses.push(...out.losses);
@@ -52,7 +61,7 @@ function createConverter(opts = {}) {
52
61
  };
53
62
  }
54
63
  if (to === "html") {
55
- const { doc: flow, losses: readLosses } = readToFlow(reader, bytes);
64
+ const { doc: flow, losses: readLosses } = readToFlow(reader, bytes, rest.now ? { now: rest.now } : void 0);
56
65
  losses.push(...readLosses);
57
66
  const html = writeHtml(flow);
58
67
  losses.push(...html.losses);
@@ -86,7 +95,7 @@ function createConverter(opts = {}) {
86
95
  if (to === "svg") {
87
96
  const fonts = conv.fonts ?? (conv.fontBytes ? { regular: conv.fontBytes } : void 0);
88
97
  if (!fonts) throw new Error("to: 'svg' requires options.fonts/fontBytes or fontProviders");
89
- const { doc: flow } = readToFlow(reader, bytes);
98
+ const { doc: flow } = readToFlow(reader, bytes, rest.now ? { now: rest.now } : void 0);
90
99
  const svg = writeSvg(layoutStyledDocument(flow.body, {
91
100
  registry: FontRegistry.fromBytes(fonts),
92
101
  ...flowRenderOptions(flow)
@@ -98,7 +107,10 @@ function createConverter(opts = {}) {
98
107
  losses
99
108
  };
100
109
  }
101
- const pdf = reader.id === "xlsx" ? await convertXlsxToPdf(bytes, conv) : await convertDocxToPdf(bytes, conv);
110
+ let pdf;
111
+ if (reader.id === "xlsx") pdf = await convertXlsxToPdf(bytes, conv);
112
+ else if (reader.produces === "sheet") pdf = await renderSheetReaderToPdf(reader, bytes, conv, rest.now);
113
+ else pdf = await convertDocxToPdf(bytes, conv);
102
114
  if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
103
115
  return {
104
116
  bytes: pdf,
@@ -111,6 +123,20 @@ function createConverter(opts = {}) {
111
123
  convert
112
124
  };
113
125
  }
126
+ async function renderSheetReaderToPdf(reader, bytes, conv, now) {
127
+ const fonts = conv.fonts ?? (conv.fontBytes ? { regular: conv.fontBytes } : await fetchFontSet({
128
+ ...conv.fontFamily ? { family: conv.fontFamily } : {},
129
+ ...conv.fontFetch ? { fetch: conv.fontFetch } : {}
130
+ }));
131
+ const { doc: flow } = readToFlow(reader, bytes, now ? { now } : void 0);
132
+ return renderStyledPdf(flow.body, {
133
+ registry: FontRegistry.fromBytes(fonts),
134
+ ...flowRenderOptions(flow),
135
+ ...conv.pdfA ? { pdfA: conv.pdfA } : {},
136
+ ...conv.info ? { info: conv.info } : {},
137
+ ...conv.section ? { section: conv.section } : {}
138
+ });
139
+ }
114
140
  async function resolveFontsViaChain(providers) {
115
141
  const chain = chainProviders(providers);
116
142
  const ask = (bold, italic) => chain.resolve({
@@ -21,6 +21,7 @@ export interface ReamConvertOptions extends Omit<StyledRenderOptions, 'registry'
21
21
  readonly strict?: boolean;
22
22
  readonly embedSource?: boolean;
23
23
  readonly signature?: SignatureOptions;
24
+ readonly now?: Date;
24
25
  }
25
26
  export declare class Ream {
26
27
  readonly flow: FlowDoc;
@@ -5,6 +5,7 @@ import { layoutStyledDocument } from "../../layout/styled-layout.js";
5
5
  import { signPdf } from "../../pdf/signature.js";
6
6
  import { renderStyledPdf, renderStyledPdfEncrypted } from "../../pdf/styled-page-renderer.js";
7
7
  import "../../pdf/index.js";
8
+ import { projectSheetDoc } from "../../excel/sheet-to-flow.js";
8
9
  import { flowRenderOptions } from "./project.js";
9
10
  import { writeDocx } from "../../word/docx-writer.js";
10
11
  import { writeXlsx } from "../../excel/xlsx-writer.js";
@@ -15,8 +16,11 @@ import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
15
16
  //#region src/core/converter/ream.ts
16
17
  var SOURCE_MIME = {
17
18
  docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
19
+ doc: "application/msword",
18
20
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
21
+ xls: "application/vnd.ms-excel",
19
22
  pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
23
+ ppt: "application/vnd.ms-powerpoint",
20
24
  pdf: "application/pdf"
21
25
  };
22
26
  var Ream = class Ream {
@@ -43,8 +47,9 @@ var Ream = class Ream {
43
47
  }
44
48
  async convertWithReport(to, options = {}) {
45
49
  const losses = [...this.losses];
50
+ const flow = this.sheet && options.now ? projectSheetDoc(this.sheet, { now: options.now }) : this.flow;
46
51
  if (to === "html") {
47
- const html = writeHtml(this.flow);
52
+ const html = writeHtml(flow);
48
53
  losses.push(...html.losses);
49
54
  this.enforceStrict(options, losses);
50
55
  return {
@@ -53,7 +58,7 @@ var Ream = class Ream {
53
58
  };
54
59
  }
55
60
  if (to === "docx") {
56
- const docx = writeDocx(this.flow);
61
+ const docx = writeDocx(flow);
57
62
  losses.push(...docx.losses);
58
63
  this.enforceStrict(options, losses);
59
64
  return {
@@ -74,9 +79,9 @@ var Ream = class Ream {
74
79
  const { fonts, registriesByFamily } = await this.resolveFonts(options, losses);
75
80
  const registry = FontRegistry.fromBytes(fonts);
76
81
  if (to === "svg") {
77
- const svg = writeSvg(layoutStyledDocument(this.flow.body, {
82
+ const svg = writeSvg(layoutStyledDocument(flow.body, {
78
83
  registry,
79
- ...flowRenderOptions(this.flow)
84
+ ...flowRenderOptions(flow)
80
85
  }));
81
86
  losses.push(...svg.losses);
82
87
  this.enforceStrict(options, losses);
@@ -86,8 +91,8 @@ var Ream = class Ream {
86
91
  };
87
92
  }
88
93
  const { fonts: _a, fontBytes: _b, fontFamily: _c, fontFetch: _d, fontProviders: _e, strict: _f, embedSource, signature, info: callerInfo, attachments: callerAttachments, ...renderOptions } = options;
89
- const info = this.flow.info || callerInfo ? {
90
- ...this.flow.info,
94
+ const info = flow.info || callerInfo ? {
95
+ ...flow.info,
91
96
  ...callerInfo
92
97
  } : void 0;
93
98
  const attachments = [...callerAttachments ?? []];
@@ -101,13 +106,13 @@ var Ream = class Ream {
101
106
  const styled = {
102
107
  registry,
103
108
  ...registriesByFamily ? { registriesByFamily } : {},
104
- ...flowRenderOptions(this.flow),
109
+ ...flowRenderOptions(flow),
105
110
  ...info ? { info } : {},
106
111
  ...attachments.length > 0 ? { attachments } : {},
107
112
  ...signature ? { signaturePlaceholder: signature } : {},
108
113
  ...renderOptions
109
114
  };
110
- let pdf = styled.encrypt ? await renderStyledPdfEncrypted(this.flow.body, styled) : renderStyledPdf(this.flow.body, styled);
115
+ let pdf = styled.encrypt ? await renderStyledPdfEncrypted(flow.body, styled) : renderStyledPdf(flow.body, styled);
111
116
  if (signature) pdf = await signPdf(pdf, signature);
112
117
  this.enforceStrict(options, losses);
113
118
  return {
@@ -3,6 +3,7 @@ export declare const FEATURES: {
3
3
  readonly text: "text";
4
4
  readonly tables: "tables";
5
5
  readonly tablesNested: "tables.nested";
6
+ readonly cellFormatting: "cellFormatting";
6
7
  readonly lists: "lists";
7
8
  readonly sections: "sections";
8
9
  readonly headersFooters: "headersFooters";
@@ -3,6 +3,7 @@ var FEATURES = {
3
3
  text: "text",
4
4
  tables: "tables",
5
5
  tablesNested: "tables.nested",
6
+ cellFormatting: "cellFormatting",
6
7
  lists: "lists",
7
8
  sections: "sections",
8
9
  headersFooters: "headersFooters",
@@ -40,6 +40,12 @@ export interface SheetFormControl {
40
40
  readonly checked?: boolean;
41
41
  readonly value?: number;
42
42
  }
43
+ export interface SheetActiveXControl {
44
+ readonly type: string;
45
+ readonly caption?: string;
46
+ readonly value?: string;
47
+ readonly groupName?: string;
48
+ }
43
49
  export interface Sheet {
44
50
  readonly name: string;
45
51
  readonly grid: ParsedWorksheet;
@@ -50,6 +56,7 @@ export interface Sheet {
50
56
  readonly slicers?: ReadonlyArray<SheetSlicer>;
51
57
  readonly comments?: ReadonlyArray<SheetComment>;
52
58
  readonly formControls?: ReadonlyArray<SheetFormControl>;
59
+ readonly activeXControls?: ReadonlyArray<SheetActiveXControl>;
53
60
  }
54
61
  export interface SheetDoc {
55
62
  readonly kind: 'sheet';
@@ -0,0 +1,19 @@
1
+ export type CfbEntryType = 'root' | 'storage' | 'stream' | 'unknown';
2
+ export interface CfbEntry {
3
+ readonly name: string;
4
+ readonly type: CfbEntryType;
5
+ readonly startSector: number;
6
+ readonly size: number;
7
+ }
8
+ export declare class CfbError extends Error {
9
+ }
10
+ export interface Cfb {
11
+ /** Every directory entry, in directory order (index 0 is the root storage). */
12
+ readonly entries: ReadonlyArray<CfbEntry>;
13
+ /** Read a stream's bytes by entry name (case-insensitive), or undefined. */
14
+ readStream: (name: string) => Uint8Array | undefined;
15
+ /** Whether a stream entry with that name exists (case-insensitive). */
16
+ hasStream: (name: string) => boolean;
17
+ }
18
+ export declare function isCfb(bytes: Uint8Array): boolean;
19
+ export declare function openCfb(bytes: Uint8Array): Cfb;
@@ -0,0 +1,158 @@
1
+ //#region src/core/ole/cfb.ts
2
+ var SIGNATURE = [
3
+ 208,
4
+ 207,
5
+ 17,
6
+ 224,
7
+ 161,
8
+ 177,
9
+ 26,
10
+ 225
11
+ ];
12
+ var ENDOFCHAIN = 4294967294;
13
+ var FREESECT = 4294967295;
14
+ var HEADER_SIZE = 512;
15
+ var DIR_ENTRY_SIZE = 128;
16
+ var MAX_TOTAL_BYTES = 512 * 1024 * 1024;
17
+ var CfbError = class extends Error {};
18
+ function isCfb(bytes) {
19
+ if (bytes.length < HEADER_SIZE) return false;
20
+ for (let i = 0; i < SIGNATURE.length; i++) if (bytes[i] !== SIGNATURE[i]) return false;
21
+ return true;
22
+ }
23
+ function openCfb(bytes) {
24
+ if (!isCfb(bytes)) throw new CfbError("not a compound file (bad signature)");
25
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
26
+ const sectorShift = view.getUint16(30, true);
27
+ const miniSectorShift = view.getUint16(32, true);
28
+ if (sectorShift !== 9 && sectorShift !== 12) throw new CfbError(`unsupported sector shift ${sectorShift}`);
29
+ if (miniSectorShift !== 6) throw new CfbError(`unsupported mini-sector shift ${miniSectorShift}`);
30
+ const sectorSize = 1 << sectorShift;
31
+ const miniSectorSize = 1 << miniSectorShift;
32
+ const miniCutoff = view.getUint32(56, true);
33
+ const totalSectors = Math.floor((bytes.length - HEADER_SIZE) / sectorSize);
34
+ const sectorOffset = (sector) => {
35
+ if (sector < 0 || sector >= totalSectors) throw new CfbError(`sector ${sector} out of range`);
36
+ return HEADER_SIZE + sector * sectorSize;
37
+ };
38
+ const firstDirSector = view.getUint32(48, true);
39
+ const firstMiniFatSector = view.getUint32(60, true);
40
+ const firstDifatSector = view.getUint32(68, true);
41
+ const numDifatSectors = view.getUint32(72, true);
42
+ const fatSectorLocs = [];
43
+ for (let i = 0; i < 109; i++) {
44
+ const loc = view.getUint32(76 + i * 4, true);
45
+ if (loc !== FREESECT && loc < totalSectors) fatSectorLocs.push(loc);
46
+ }
47
+ const perDifat = sectorSize / 4 - 1;
48
+ let difatSector = firstDifatSector;
49
+ for (let guard = 0; difatSector !== ENDOFCHAIN && difatSector !== FREESECT; guard++) {
50
+ if (guard > numDifatSectors || guard > totalSectors) throw new CfbError("DIFAT chain too long (corrupt)");
51
+ const off = sectorOffset(difatSector);
52
+ for (let i = 0; i < perDifat; i++) {
53
+ const loc = view.getUint32(off + i * 4, true);
54
+ if (loc !== FREESECT && loc < totalSectors) fatSectorLocs.push(loc);
55
+ }
56
+ difatSector = view.getUint32(off + perDifat * 4, true);
57
+ }
58
+ const entriesPerSector = sectorSize / 4;
59
+ const fat = new Uint32Array(fatSectorLocs.length * entriesPerSector);
60
+ for (let s = 0; s < fatSectorLocs.length; s++) {
61
+ const off = sectorOffset(fatSectorLocs[s]);
62
+ for (let i = 0; i < entriesPerSector; i++) fat[s * entriesPerSector + i] = view.getUint32(off + i * 4, true);
63
+ }
64
+ const followChain = (start) => {
65
+ const out = [];
66
+ let s = start;
67
+ while (s !== ENDOFCHAIN && s !== FREESECT) {
68
+ if (s >= totalSectors || out.length > totalSectors) throw new CfbError("FAT chain corrupt (out of range or cyclic)");
69
+ out.push(s);
70
+ s = fat[s] ?? ENDOFCHAIN;
71
+ }
72
+ return out;
73
+ };
74
+ const readChainBytes = (start, limit) => {
75
+ const sectors = followChain(start);
76
+ const total = sectors.length * sectorSize;
77
+ if (total > limit) throw new CfbError("stream exceeds size limit");
78
+ const out = new Uint8Array(total);
79
+ for (let i = 0; i < sectors.length; i++) {
80
+ const off = sectorOffset(sectors[i]);
81
+ out.set(bytes.subarray(off, off + sectorSize), i * sectorSize);
82
+ }
83
+ return out;
84
+ };
85
+ const dirBytes = readChainBytes(firstDirSector, MAX_TOTAL_BYTES);
86
+ const dirView = new DataView(dirBytes.buffer, dirBytes.byteOffset, dirBytes.byteLength);
87
+ const entries = [];
88
+ const entryCount = Math.floor(dirBytes.length / DIR_ENTRY_SIZE);
89
+ for (let i = 0; i < entryCount; i++) {
90
+ const base = i * DIR_ENTRY_SIZE;
91
+ const objType = dirView.getUint8(base + 66);
92
+ if (objType !== 1 && objType !== 2 && objType !== 5) continue;
93
+ const name = decodeName(dirBytes, base, dirView.getUint16(base + 64, true));
94
+ const sizeLow = dirView.getUint32(base + 120, true);
95
+ const size = dirView.getUint32(base + 124, true) * 4294967296 + sizeLow;
96
+ entries.push({
97
+ name,
98
+ type: objType === 5 ? "root" : objType === 1 ? "storage" : "stream",
99
+ startSector: dirView.getUint32(base + 116, true),
100
+ size
101
+ });
102
+ }
103
+ const root = entries.find((e) => e.type === "root");
104
+ if (!root) throw new CfbError("compound file has no root entry");
105
+ let miniStream;
106
+ let miniFat;
107
+ const ensureMini = () => {
108
+ if (miniStream && miniFat) return;
109
+ miniStream = readChainBytes(root.startSector, MAX_TOTAL_BYTES).subarray(0, root.size);
110
+ const mfBytes = firstMiniFatSector === ENDOFCHAIN ? new Uint8Array(0) : readChainBytes(firstMiniFatSector, MAX_TOTAL_BYTES);
111
+ const mfView = new DataView(mfBytes.buffer, mfBytes.byteOffset, mfBytes.byteLength);
112
+ miniFat = new Uint32Array(Math.floor(mfBytes.length / 4));
113
+ for (let i = 0; i < miniFat.length; i++) miniFat[i] = mfView.getUint32(i * 4, true);
114
+ };
115
+ const readMiniStream = (entry) => {
116
+ ensureMini();
117
+ const stream = miniStream;
118
+ const mfat = miniFat;
119
+ const maxMini = Math.floor(stream.length / miniSectorSize);
120
+ const out = new Uint8Array(entry.size);
121
+ let mini = entry.startSector;
122
+ let written = 0;
123
+ let guard = 0;
124
+ while (mini !== ENDOFCHAIN && mini !== FREESECT && written < entry.size) {
125
+ if (mini >= maxMini || guard++ > mfat.length) throw new CfbError("mini-FAT chain corrupt (out of range or cyclic)");
126
+ const off = mini * miniSectorSize;
127
+ const n = Math.min(miniSectorSize, entry.size - written);
128
+ out.set(stream.subarray(off, off + n), written);
129
+ written += n;
130
+ mini = mfat[mini] ?? ENDOFCHAIN;
131
+ }
132
+ return out;
133
+ };
134
+ const readEntry = (entry) => {
135
+ if (entry.size === 0) return new Uint8Array(0);
136
+ if (entry.size > MAX_TOTAL_BYTES) throw new CfbError("stream exceeds size limit");
137
+ if (entry.size >= miniCutoff) return readChainBytes(entry.startSector, MAX_TOTAL_BYTES).subarray(0, entry.size);
138
+ return readMiniStream(entry);
139
+ };
140
+ const byName = /* @__PURE__ */ new Map();
141
+ for (const e of entries) if (e.type === "stream" && !byName.has(e.name.toLowerCase())) byName.set(e.name.toLowerCase(), e);
142
+ return {
143
+ entries,
144
+ hasStream: (name) => byName.has(name.toLowerCase()),
145
+ readStream: (name) => {
146
+ const e = byName.get(name.toLowerCase());
147
+ return e ? readEntry(e) : void 0;
148
+ }
149
+ };
150
+ }
151
+ function decodeName(dir, base, nameLen) {
152
+ const len = Math.max(0, Math.min(64, nameLen) - 2);
153
+ let out = "";
154
+ for (let i = 0; i < len; i += 2) out += String.fromCharCode(dir[base + i] | dir[base + i + 1] << 8);
155
+ return out;
156
+ }
157
+ //#endregion
158
+ export { isCfb, openCfb };
@@ -1 +1 @@
1
- export type { CellType, WorksheetCell, ColumnWidth, MergedRange, RowHeight, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, ParsedWorksheet, XlsxFont, XlsxFill, XlsxBorderStyleName, XlsxBorderEdge, XlsxBorder, XlsxHorizontalAlign, XlsxVerticalAlign, XlsxCellAlignment, XlsxCellXf, XlsxStyles, DefinedName, Dxf, CfOperator, CfRuleCellIs, CfvoType, Cfvo, CfRuleColorScale, CfRuleDataBar, CfRuleIconSet, CfRuleTop10, CfRuleAboveAverage, CfRuleDupUnique, CfRuleText, CfRule, ConditionalFormat, DataValidationType, DataValidation, HyperlinkRef, HeaderFooter, FormControlRef, SheetRichRun, SparklineKind, ParsedSparkline, ExcelTable, PivotTable, SheetPane, } from './types.js';
1
+ export type { CellType, WorksheetCell, ColumnWidth, MergedRange, RowHeight, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, ParsedWorksheet, XlsxFont, XlsxFill, XlsxBorderStyleName, XlsxBorderEdge, XlsxBorder, XlsxHorizontalAlign, XlsxVerticalAlign, XlsxCellAlignment, XlsxCellXf, XlsxStyles, DefinedName, Dxf, CfOperator, CfRuleCellIs, CfvoType, Cfvo, CfRuleColorScale, CfRuleDataBar, CfRuleIconSet, CfRuleTop10, CfRuleAboveAverage, CfRuleDupUnique, CfRuleText, CfRuleExpression, CfRuleTimePeriod, TimePeriodKind, CfRule, ConditionalFormat, DataValidationType, DataValidation, HyperlinkRef, HeaderFooter, FormControlRef, OleObjectRef, SheetRichRun, SparklineKind, ParsedSparkline, ExcelTable, PivotTable, SheetPane, } from './types.js';
@@ -62,6 +62,7 @@ export interface ParsedWorksheet {
62
62
  readonly hyperlinks?: ReadonlyArray<HyperlinkRef>;
63
63
  readonly headerFooter?: HeaderFooter;
64
64
  readonly formControls?: ReadonlyArray<FormControlRef>;
65
+ readonly oleObjects?: ReadonlyArray<OleObjectRef>;
65
66
  readonly sparklines?: ReadonlyArray<ParsedSparkline>;
66
67
  readonly tablePartRelIds?: ReadonlyArray<string>;
67
68
  readonly tables?: ReadonlyArray<ExcelTable>;
@@ -236,7 +237,21 @@ export interface CfRuleText {
236
237
  readonly dxfId: number;
237
238
  readonly formula?: string;
238
239
  }
239
- export type CfRule = CfRuleCellIs | CfRuleColorScale | CfRuleDataBar | CfRuleIconSet | CfRuleTop10 | CfRuleAboveAverage | CfRuleDupUnique | CfRuleText;
240
+ export interface CfRuleExpression {
241
+ readonly type: 'expression';
242
+ readonly priority: number;
243
+ readonly formula: string;
244
+ readonly dxfId: number;
245
+ }
246
+ export type TimePeriodKind = 'today' | 'yesterday' | 'tomorrow' | 'last7Days' | 'thisWeek' | 'lastWeek' | 'nextWeek' | 'thisMonth' | 'lastMonth' | 'nextMonth';
247
+ export interface CfRuleTimePeriod {
248
+ readonly type: 'timePeriod';
249
+ readonly priority: number;
250
+ readonly timePeriod: TimePeriodKind;
251
+ readonly dxfId: number;
252
+ readonly formula?: string;
253
+ }
254
+ export type CfRule = CfRuleCellIs | CfRuleColorScale | CfRuleDataBar | CfRuleIconSet | CfRuleTop10 | CfRuleAboveAverage | CfRuleDupUnique | CfRuleText | CfRuleExpression | CfRuleTimePeriod;
240
255
  export interface ConditionalFormat {
241
256
  readonly ranges: ReadonlyArray<MergedRange>;
242
257
  readonly rules: ReadonlyArray<CfRule>;
@@ -256,6 +271,10 @@ export interface FormControlRef {
256
271
  readonly name?: string;
257
272
  readonly relId: string;
258
273
  }
274
+ export interface OleObjectRef {
275
+ readonly progId?: string;
276
+ readonly relId: string;
277
+ }
259
278
  export interface SheetRichRun {
260
279
  readonly text: string;
261
280
  readonly bold?: boolean;
@@ -0,0 +1,9 @@
1
+ export interface ActiveXProps {
2
+ readonly caption?: string;
3
+ readonly value?: string;
4
+ readonly groupName?: string;
5
+ }
6
+ export declare function parseActiveX(data: Uint8Array): ActiveXProps;
7
+ export declare function activeXType(progId: string | undefined): string;
8
+ export declare function activeXBinRelId(xmlData: Uint8Array): string | undefined;
9
+ export declare function parseActiveXBin(data: Uint8Array): ActiveXProps;