reamkit 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +2 -2
  2. package/dist/esm/core/converter/facade.js +6 -1
  3. package/dist/esm/core/converter/ream.js +2 -1
  4. package/dist/esm/core/document-model/types.d.ts +9 -1
  5. package/dist/esm/core/drawingml/chart-serializer.d.ts +2 -0
  6. package/dist/esm/core/drawingml/chart-serializer.js +53 -0
  7. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  8. package/dist/esm/core/spreadsheet-model/types.d.ts +5 -0
  9. package/dist/esm/excel/column-bands.d.ts +7 -0
  10. package/dist/esm/excel/column-bands.js +87 -0
  11. package/dist/esm/excel/conditional-format.js +22 -0
  12. package/dist/esm/excel/print-model.js +33 -11
  13. package/dist/esm/excel/worksheet-parser.js +26 -0
  14. package/dist/esm/excel/xlsx-writer.js +88 -17
  15. package/dist/esm/html/html-writer.js +100 -5
  16. package/dist/esm/layout/styled-layout.js +88 -19
  17. package/dist/esm/pdf-reader/cmap.d.ts +5 -0
  18. package/dist/esm/pdf-reader/cmap.js +72 -0
  19. package/dist/esm/pdf-reader/content.d.ts +52 -0
  20. package/dist/esm/pdf-reader/content.js +420 -0
  21. package/dist/esm/pdf-reader/crypto.d.ts +7 -0
  22. package/dist/esm/pdf-reader/crypto.js +609 -0
  23. package/dist/esm/pdf-reader/decrypt.d.ts +5 -0
  24. package/dist/esm/pdf-reader/decrypt.js +199 -0
  25. package/dist/esm/pdf-reader/document.d.ts +30 -0
  26. package/dist/esm/pdf-reader/document.js +413 -0
  27. package/dist/esm/pdf-reader/flow-build.d.ts +19 -0
  28. package/dist/esm/pdf-reader/flow-build.js +126 -0
  29. package/dist/esm/pdf-reader/font.d.ts +4 -0
  30. package/dist/esm/pdf-reader/font.js +69 -0
  31. package/dist/esm/pdf-reader/image-decode.d.ts +15 -0
  32. package/dist/esm/pdf-reader/image-decode.js +442 -0
  33. package/dist/esm/pdf-reader/images.d.ts +16 -0
  34. package/dist/esm/pdf-reader/images.js +90 -0
  35. package/dist/esm/pdf-reader/layout.d.ts +3 -0
  36. package/dist/esm/pdf-reader/layout.js +103 -0
  37. package/dist/esm/pdf-reader/lexer.d.ts +43 -0
  38. package/dist/esm/pdf-reader/lexer.js +250 -0
  39. package/dist/esm/pdf-reader/parser.d.ts +10 -0
  40. package/dist/esm/pdf-reader/parser.js +86 -0
  41. package/dist/esm/pdf-reader/png-encode.d.ts +2 -0
  42. package/dist/esm/pdf-reader/png-encode.js +98 -0
  43. package/dist/esm/pdf-reader/predictor.d.ts +7 -0
  44. package/dist/esm/pdf-reader/predictor.js +61 -0
  45. package/dist/esm/pdf-reader/reader.d.ts +4 -0
  46. package/dist/esm/pdf-reader/reader.js +50 -0
  47. package/dist/esm/pdf-reader/struct-tree.d.ts +14 -0
  48. package/dist/esm/pdf-reader/struct-tree.js +92 -0
  49. package/dist/esm/pdf-reader/tagged.d.ts +3 -0
  50. package/dist/esm/pdf-reader/tagged.js +141 -0
  51. package/dist/esm/pdf-reader/text.d.ts +3 -0
  52. package/dist/esm/pdf-reader/text.js +65 -0
  53. package/dist/esm/pdf-reader/vector.d.ts +12 -0
  54. package/dist/esm/pdf-reader/vector.js +54 -0
  55. package/dist/esm/word/docx-writer.js +96 -7
  56. package/dist/esm/word/omml-serializer.d.ts +2 -0
  57. package/dist/esm/word/omml-serializer.js +48 -0
  58. package/package.json +2 -2
package/README.md CHANGED
@@ -30,7 +30,7 @@ import { Ream } from 'reamkit';
30
30
  // e.g. from an <input type="file"> or a fetch() — anything that yields bytes.
31
31
  const bytes = new Uint8Array(await file.arrayBuffer());
32
32
 
33
- const doc = Ream.parse(bytes); // docx or xlsx — sniffed
33
+ const doc = Ream.parse(bytes); // docx, xlsx or pdf — sniffed
34
34
  const pdf = await doc.convert('pdf'); // async — fetches a font if needed
35
35
  const svg = await doc.convert('svg'); // same parse, different target
36
36
  const html = await doc.convert('html'); // flowed HTML — needs no fonts at all
@@ -130,7 +130,7 @@ rendering — inspect or analyze it without converting:
130
130
 
131
131
  ```ts
132
132
  const doc = Ream.parse(bytes);
133
- doc.format; // 'docx' | 'xlsx'
133
+ doc.format; // 'docx' | 'xlsx' | 'pdf'
134
134
  doc.flow.body; // paragraphs / tables / images / charts …
135
135
  doc.losses; // read-time losses
136
136
  ```
@@ -13,6 +13,7 @@ import { writeHtml } from "../../html/html-writer.js";
13
13
  import { writeSvg } from "../../svg/svg-writer.js";
14
14
  import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
15
15
  import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
16
+ import { pdfReader } from "../../pdf-reader/reader.js";
16
17
  //#region src/core/converter/facade.ts
17
18
  function toFlowDoc(doc) {
18
19
  return doc.kind === "sheet" ? projectSheetDoc(doc) : doc;
@@ -24,7 +25,11 @@ function readToFlow(reader, bytes) {
24
25
  losses
25
26
  };
26
27
  }
27
- var DEFAULT_READERS = [docxReader, xlsxReader];
28
+ var DEFAULT_READERS = [
29
+ docxReader,
30
+ xlsxReader,
31
+ pdfReader
32
+ ];
28
33
  function createConverter(opts = {}) {
29
34
  const readers = opts.readers ?? DEFAULT_READERS;
30
35
  const detect = (bytes) => readers.find((r) => r.sniff(bytes));
@@ -15,7 +15,8 @@ import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
15
15
  //#region src/core/converter/ream.ts
16
16
  var SOURCE_MIME = {
17
17
  docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
18
- xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
18
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
19
+ pdf: "application/pdf"
19
20
  };
20
21
  var Ream = class Ream {
21
22
  constructor(flow, sheet, losses, source, readerId) {
@@ -226,10 +226,14 @@ export interface CellDataBar {
226
226
  readonly colorHex: string;
227
227
  readonly startFraction?: number;
228
228
  }
229
- export type CellIconShape = 'circle' | 'square' | 'diamond' | 'triangleUp' | 'triangleDown' | 'triangleRight';
229
+ export type CellIconShape = 'circle' | 'square' | 'diamond' | 'triangleUp' | 'triangleDown' | 'triangleRight' | 'check' | 'cross' | 'exclamation' | 'bars' | 'pie';
230
230
  export interface CellIcon {
231
231
  readonly shape: CellIconShape;
232
232
  readonly colorHex: string;
233
+ readonly fill?: {
234
+ readonly filled: number;
235
+ readonly levels: number;
236
+ };
233
237
  }
234
238
  export interface CellSparkline {
235
239
  readonly kind: 'line' | 'column' | 'winLoss';
@@ -273,6 +277,10 @@ export interface TableProperties {
273
277
  readonly defaultCellMargins?: CellMargins;
274
278
  readonly borders?: CellBorders;
275
279
  readonly alignment?: 'left' | 'center' | 'right';
280
+ readonly frozen?: {
281
+ readonly rows: number;
282
+ readonly cols: number;
283
+ };
276
284
  }
277
285
  export interface TableCell {
278
286
  readonly properties: CellProperties;
@@ -0,0 +1,2 @@
1
+ import { Chart } from '../document-model/index.js';
2
+ export declare function chartSpaceXml(chart: Chart): string;
@@ -0,0 +1,53 @@
1
+ //#region src/core/drawingml/chart-serializer.ts
2
+ var C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart";
3
+ var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
4
+ var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
5
+ var GROUP_TAG = {
6
+ bar: "c:barChart",
7
+ line: "c:lineChart",
8
+ pie: "c:pieChart",
9
+ area: "c:areaChart",
10
+ scatter: "c:scatterChart",
11
+ unknown: "c:barChart"
12
+ };
13
+ var CAT_AX_ID = 111111111;
14
+ var VAL_AX_ID = 222222222;
15
+ function chartSpaceXml(chart) {
16
+ const isScatter = chart.type === "scatter";
17
+ const isPie = chart.type === "pie" || chart.doughnut === true;
18
+ const groupTag = chart.doughnut ? "c:doughnutChart" : GROUP_TAG[chart.type] ?? "c:barChart";
19
+ const inner = (chart.type === "bar" ? `<c:barDir val="${chart.barDir ?? "col"}"/>` : "") + (chart.grouping ? `<c:grouping val="${chart.grouping}"/>` : "") + "<c:varyColors val=\"0\"/>" + chart.series.map((s, i) => seriesXml(s, i, isScatter, chart.categories)).join("") + (chart.showValues ? "<c:dLbls><c:showVal val=\"1\"/></c:dLbls>" : "") + (chart.doughnut ? "<c:holeSize val=\"50\"/>" : "") + (isPie ? "" : `<c:axId val="${CAT_AX_ID}"/><c:axId val="${VAL_AX_ID}"/>`);
20
+ const axes = isPie ? "" : isScatter ? valAx(CAT_AX_ID, "b", VAL_AX_ID, chart.catAxisTitle) + valAx(VAL_AX_ID, "l", CAT_AX_ID, chart.valAxisTitle) : catAx(CAT_AX_ID, VAL_AX_ID, chart.catAxisTitle) + valAx(VAL_AX_ID, "l", CAT_AX_ID, chart.valAxisTitle);
21
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><c:chartSpace xmlns:c="${C_NS}" xmlns:a="${A_NS}" xmlns:r="${R_NS}"><c:chart>` + titleXml(chart.title) + `<c:autoTitleDeleted val="${chart.title ? 0 : 1}"/><c:plotArea><c:layout/><${groupTag}>${inner}</${groupTag}>${axes}</c:plotArea>` + (chart.hasLegend ? `<c:legend><c:legendPos val="${chart.legendPos ?? "r"}"/><c:overlay val="0"/></c:legend>` : "") + "<c:plotVisOnly val=\"1\"/></c:chart></c:chartSpace>";
22
+ }
23
+ function seriesXml(s, idx, isScatter, categories) {
24
+ const tx = s.name ? `<c:tx><c:strRef><c:f>Sheet1!$A$${idx + 1}</c:f><c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>${esc(s.name)}</c:v></c:pt></c:strCache></c:strRef></c:tx>` : "";
25
+ const spPr = s.colorHex ? `<c:spPr><a:solidFill><a:srgbClr val="${s.colorHex}"/></a:solidFill></c:spPr>` : "";
26
+ const dPts = (s.pointColors ?? []).map((p) => `<c:dPt><c:idx val="${p.idx}"/><c:bubble3D val="0"/><c:spPr><a:solidFill><a:srgbClr val="${p.colorHex}"/></a:solidFill></c:spPr></c:dPt>`).join("");
27
+ const cat = isScatter ? `<c:xVal>${numRef("A", s.xValues ?? [])}</c:xVal>` : categories.length > 0 ? `<c:cat>${strRef(categories)}</c:cat>` : "";
28
+ const val = isScatter ? `<c:yVal>${numRef("B", s.values)}</c:yVal>` : `<c:val>${numRef("B", s.values)}</c:val>`;
29
+ return `<c:ser><c:idx val="${idx}"/><c:order val="${idx}"/>` + tx + spPr + dPts + cat + val + "</c:ser>";
30
+ }
31
+ function strRef(values) {
32
+ const pts = values.map((v, i) => `<c:pt idx="${i}"><c:v>${esc(v)}</c:v></c:pt>`).join("");
33
+ return `<c:strRef><c:f>Sheet1!\$A\$1</c:f><c:strCache><c:ptCount val="${values.length}"/>${pts}</c:strCache></c:strRef>`;
34
+ }
35
+ function numRef(col, values) {
36
+ const pts = values.map((v, i) => `<c:pt idx="${i}"><c:v>${v}</c:v></c:pt>`).join("");
37
+ return `<c:numRef><c:f>Sheet1!$${col}$1</c:f><c:numCache><c:formatCode>General</c:formatCode><c:ptCount val="${values.length}"/>${pts}</c:numCache></c:numRef>`;
38
+ }
39
+ function titleXml(title) {
40
+ if (!title) return "";
41
+ return `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${esc(title)}</a:t></a:r></a:p></c:rich></c:tx><c:overlay val="0"/></c:title>`;
42
+ }
43
+ function catAx(id, crossId, title) {
44
+ return `<c:catAx><c:axId val="${id}"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="b"/>${titleXml(title)}<c:crossAx val="${crossId}"/></c:catAx>`;
45
+ }
46
+ function valAx(id, pos, crossId, title) {
47
+ return `<c:valAx><c:axId val="${id}"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="${pos}"/>${titleXml(title)}<c:crossAx val="${crossId}"/></c:valAx>`;
48
+ }
49
+ function esc(s) {
50
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
51
+ }
52
+ //#endregion
53
+ export { chartSpaceXml };
@@ -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, CfRule, ConditionalFormat, SparklineKind, ParsedSparkline, ExcelTable, } 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, CfRule, ConditionalFormat, SparklineKind, ParsedSparkline, ExcelTable, SheetPane, } from './types.js';
@@ -61,6 +61,11 @@ export interface ParsedWorksheet {
61
61
  readonly sparklines?: ReadonlyArray<ParsedSparkline>;
62
62
  readonly tablePartRelIds?: ReadonlyArray<string>;
63
63
  readonly tables?: ReadonlyArray<ExcelTable>;
64
+ readonly pane?: SheetPane;
65
+ }
66
+ export interface SheetPane {
67
+ readonly frozenRows: number;
68
+ readonly frozenCols: number;
64
69
  }
65
70
  export interface ExcelTable {
66
71
  readonly ref: MergedRange;
@@ -0,0 +1,7 @@
1
+ import { BodyElement, TableProperties, TableRow } from '../core/document-model/index.js';
2
+ export interface ColumnBand {
3
+ readonly start: number;
4
+ readonly end: number;
5
+ }
6
+ export declare function computeColumnBands(columnWidths: ReadonlyArray<number>, contentWidthTwips: number, colBreaks: ReadonlySet<number>): Array<ColumnBand>;
7
+ export declare function bandedTables(rows: ReadonlyArray<TableRow>, columnWidths: ReadonlyArray<number>, bands: ReadonlyArray<ColumnBand>, properties: TableProperties): Array<BodyElement>;
@@ -0,0 +1,87 @@
1
+ import { twipsToPt } from "../core/ir/units.js";
2
+ //#region src/excel/column-bands.ts
3
+ function computeColumnBands(columnWidths, contentWidthTwips, colBreaks) {
4
+ const n = columnWidths.length;
5
+ if (n === 0) return [];
6
+ const bands = [];
7
+ let start = 0;
8
+ let acc = 0;
9
+ for (let c = 0; c < n; c++) {
10
+ const w = columnWidths[c] ?? 0;
11
+ const atBreak = c > start && colBreaks.has(c);
12
+ const overflows = c > start && acc + w > contentWidthTwips;
13
+ if (atBreak || overflows) {
14
+ bands.push({
15
+ start,
16
+ end: c - 1
17
+ });
18
+ start = c;
19
+ acc = 0;
20
+ }
21
+ acc += w;
22
+ }
23
+ bands.push({
24
+ start,
25
+ end: n - 1
26
+ });
27
+ return bands;
28
+ }
29
+ function bandedTables(rows, columnWidths, bands, properties) {
30
+ return bands.map((band, bandIndex) => {
31
+ return {
32
+ kind: "table",
33
+ table: {
34
+ properties,
35
+ grid: columnWidths.slice(band.start, band.end + 1).map((w) => twipsToPt(w)),
36
+ rows: rows.map((row, rowIndex) => {
37
+ const cells = sliceRowCells(row.cells, band);
38
+ return {
39
+ properties: bandIndex > 0 && rowIndex === 0 ? {
40
+ ...row.properties,
41
+ pageBreakBefore: true
42
+ } : row.properties,
43
+ cells
44
+ };
45
+ })
46
+ }
47
+ };
48
+ });
49
+ }
50
+ function sliceRowCells(cells, band) {
51
+ const out = [];
52
+ let col = 0;
53
+ for (const cell of cells) {
54
+ const span = cell.properties.colSpan ?? 1;
55
+ const cellStart = col;
56
+ const cellEnd = col + span - 1;
57
+ col += span;
58
+ if (cellEnd < band.start || cellStart > band.end) continue;
59
+ const clampedSpan = Math.min(cellEnd, band.end) - Math.max(cellStart, band.start) + 1;
60
+ if (cellStart >= band.start) out.push(clampedSpan === span ? cell : withColSpan(cell, clampedSpan));
61
+ else out.push(blankCell(clampedSpan));
62
+ }
63
+ return out;
64
+ }
65
+ function withColSpan(cell, span) {
66
+ const props = { ...cell.properties };
67
+ if (span > 1) props.colSpan = span;
68
+ else delete props.colSpan;
69
+ return {
70
+ ...cell,
71
+ properties: props
72
+ };
73
+ }
74
+ function blankCell(span) {
75
+ return {
76
+ properties: span > 1 ? { colSpan: span } : {},
77
+ content: [{
78
+ kind: "paragraph",
79
+ paragraph: {
80
+ properties: {},
81
+ runs: []
82
+ }
83
+ }]
84
+ };
85
+ }
86
+ //#endregion
87
+ export { bandedTables, computeColumnBands };
@@ -256,6 +256,22 @@ function iconBucket(thresholds, value) {
256
256
  }
257
257
  function iconToCell(setName, count, index, reverse) {
258
258
  const e = reverse ? count - 1 - index : index;
259
+ if (/Rating/i.test(setName)) return {
260
+ shape: "bars",
261
+ colorHex: METER_HEX,
262
+ fill: {
263
+ filled: e + 1,
264
+ levels: count
265
+ }
266
+ };
267
+ if (/Quarters/i.test(setName)) return {
268
+ shape: "pie",
269
+ colorHex: METER_HEX,
270
+ fill: {
271
+ filled: e,
272
+ levels: count - 1
273
+ }
274
+ };
259
275
  const colorHex = /Gray/i.test(setName) ? GRAY_HEX : iconColor(count, e);
260
276
  return {
261
277
  shape: iconShape(setName, e, count),
@@ -263,12 +279,18 @@ function iconToCell(setName, count, index, reverse) {
263
279
  };
264
280
  }
265
281
  var GRAY_HEX = "808080";
282
+ var METER_HEX = "595959";
266
283
  function iconShape(setName, e, count) {
267
284
  if (setName.includes("Arrows")) {
268
285
  if (e <= 0) return "triangleDown";
269
286
  if (e >= count - 1) return "triangleUp";
270
287
  return "triangleRight";
271
288
  }
289
+ if (setName.includes("Symbols")) {
290
+ if (e <= 0) return "cross";
291
+ if (e >= count - 1) return "check";
292
+ return "exclamation";
293
+ }
272
294
  if (setName.includes("Signs")) {
273
295
  if (e <= 0) return "diamond";
274
296
  if (e >= count - 1) return "circle";
@@ -2,6 +2,7 @@ import { eighthPtToPt, halfPtToPt, twipsToPt } from "../core/ir/units.js";
2
2
  import { parseAreaRef, parseTitleRowRange } from "./defined-name-ref.js";
3
3
  import { applyNumberFormat } from "./number-format.js";
4
4
  import "./index.js";
5
+ import { bandedTables, computeColumnBands } from "./column-bands.js";
5
6
  import { buildConditionalFormatter } from "./conditional-format.js";
6
7
  var TWIPS_PER_POINT = 20;
7
8
  var TWIPS_PER_INCH = 1440;
@@ -291,20 +292,41 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
291
292
  width: eighthPtToPt(4)
292
293
  };
293
294
  const centered = worksheet.printOptions?.horizontalCentered === true;
295
+ const tableProperties = {
296
+ ...print.gridLines ? { borders: {
297
+ top: thin,
298
+ bottom: thin,
299
+ left: thin,
300
+ right: thin,
301
+ insideH: thin,
302
+ insideV: thin
303
+ } } : {},
304
+ ...centered ? { alignment: "center" } : {}
305
+ };
306
+ const contentWidthTwips = sheetContentWidthTwips(worksheet);
307
+ const colBreaksLocal = /* @__PURE__ */ new Set();
308
+ for (const brk of worksheet.colBreaks ?? []) {
309
+ const local = brk - colStart;
310
+ if (local > 0 && local < colCount) colBreaksLocal.add(local);
311
+ }
312
+ const fitWide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
313
+ const bandWidths = scaled ? columnWidths.map((w) => Math.max(1, Math.floor(w * printScale))) : columnWidths;
314
+ const bandTotal = bandWidths.reduce((sum, w) => sum + w, 0);
315
+ if (colCount > 1 && (!scaled || fitWide > 1) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
316
+ const bands = computeColumnBands(bandWidths, contentWidthTwips, colBreaksLocal);
317
+ if (bands.length > 1) return bandedTables(rows, bandWidths, bands, tableProperties);
318
+ }
319
+ const frozen = worksheet.pane && (worksheet.pane.frozenRows > 0 || worksheet.pane.frozenCols > 0) ? {
320
+ rows: worksheet.pane.frozenRows,
321
+ cols: worksheet.pane.frozenCols
322
+ } : void 0;
294
323
  return [{
295
324
  kind: "table",
296
325
  table: {
297
- properties: {
298
- ...print.gridLines ? { borders: {
299
- top: thin,
300
- bottom: thin,
301
- left: thin,
302
- right: thin,
303
- insideH: thin,
304
- insideV: thin
305
- } } : {},
306
- ...centered ? { alignment: "center" } : {}
307
- },
326
+ properties: frozen ? {
327
+ ...tableProperties,
328
+ frozen
329
+ } : tableProperties,
308
330
  grid: columnWidths.map((w) => twipsToPt(w)),
309
331
  rows
310
332
  }
@@ -33,6 +33,7 @@ function parseWorksheet(data) {
33
33
  const printOptions = parsePrintOptions(wsObj);
34
34
  const rowBreaks = parseBreaks(wsObj, "rowBreaks");
35
35
  const colBreaks = parseBreaks(wsObj, "colBreaks");
36
+ const pane = parsePane(wsObj);
36
37
  const drawingNode = wsObj["drawing"];
37
38
  const drawingRelId = drawingNode && typeof drawingNode === "object" ? strAttr(drawingNode, "id") : void 0;
38
39
  const conditionalFormats = parseConditionalFormatting(wsObj);
@@ -45,6 +46,7 @@ function parseWorksheet(data) {
45
46
  ...printOptions ? { printOptions } : {},
46
47
  ...rowBreaks.length > 0 ? { rowBreaks } : {},
47
48
  ...colBreaks.length > 0 ? { colBreaks } : {},
49
+ ...pane ? { pane } : {},
48
50
  ...drawingRelId !== void 0 ? { drawingRelId } : {},
49
51
  ...conditionalFormats.length > 0 ? { conditionalFormats } : {},
50
52
  ...sparklines.length > 0 ? { sparklines } : {},
@@ -181,6 +183,30 @@ function parsePrintOptions(ws) {
181
183
  ...verticalCentered !== void 0 ? { verticalCentered } : {}
182
184
  };
183
185
  }
186
+ function parsePane(ws) {
187
+ const views = ws["sheetViews"];
188
+ if (!views || typeof views !== "object") return void 0;
189
+ const viewRaw = views["sheetView"];
190
+ const view = Array.isArray(viewRaw) ? viewRaw[0] : viewRaw;
191
+ if (!view || typeof view !== "object") return void 0;
192
+ const paneNode = view["pane"];
193
+ if (!paneNode || typeof paneNode !== "object") return void 0;
194
+ const pane = paneNode;
195
+ const state = strAttr(pane, "state");
196
+ if (state !== "frozen" && state !== "frozenSplit") return void 0;
197
+ const count = (key) => {
198
+ const raw = strAttr(pane, key);
199
+ const n = raw !== void 0 ? Number(raw) : NaN;
200
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
201
+ };
202
+ const frozenCols = count("xSplit");
203
+ const frozenRows = count("ySplit");
204
+ if (frozenCols === 0 && frozenRows === 0) return void 0;
205
+ return {
206
+ frozenRows,
207
+ frozenCols
208
+ };
209
+ }
184
210
  function parseBreaks(ws, tag) {
185
211
  const node = ws[tag];
186
212
  if (!node || typeof node !== "object") return [];
@@ -1,13 +1,22 @@
1
1
  import { FEATURES } from "../core/ir/features.js";
2
2
  import { buildOpcPackage } from "../core/opc/opc-writer.js";
3
3
  import "../core/opc/index.js";
4
+ import { chartSpaceXml } from "../core/drawingml/chart-serializer.js";
4
5
  //#region src/excel/xlsx-writer.ts
5
6
  var encoder = new TextEncoder();
7
+ var EMU_PER_PT = 12700;
6
8
  var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
7
9
  var REL_WORKSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
8
10
  var REL_SHARED_STRINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
9
11
  var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
10
12
  var REL_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
13
+ var REL_DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
14
+ var REL_CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
15
+ var CT_DRAWING = "application/vnd.openxmlformats-officedocument.drawing+xml";
16
+ var CT_CHART = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
17
+ var XDR_NS = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
18
+ var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
19
+ var C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart";
11
20
  var CT_WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
12
21
  var CT_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
13
22
  var CT_SHARED_STRINGS = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
@@ -19,16 +28,20 @@ function writeXlsx(sheet) {
19
28
  const losses = [];
20
29
  const worksheetParts = [];
21
30
  const tableParts = [];
31
+ const drawingParts = [];
22
32
  const worksheetRels = [];
33
+ const drawingRels = [];
23
34
  const sheetEntries = [];
24
35
  const workbookRels = [];
25
36
  let tableCounter = 0;
37
+ let drawingCounter = 0;
38
+ let chartCounter = 0;
26
39
  sheet.sheets.forEach((s, i) => {
27
40
  const idx = i + 1;
28
41
  const rid = `rId${idx}`;
29
42
  const wsPath = `xl/worksheets/sheet${idx}.xml`;
43
+ const wsRels = [];
30
44
  const tableRelIds = [];
31
- const wsTableRels = [];
32
45
  for (const t of s.grid.tables ?? []) {
33
46
  const tid = ++tableCounter;
34
47
  const tRid = `rIdTbl${tid}`;
@@ -38,15 +51,23 @@ function writeXlsx(sheet) {
38
51
  data: encoder.encode(tableXml(t, tid)),
39
52
  contentType: CT_TABLE
40
53
  });
41
- wsTableRels.push(rel(tRid, REL_TABLE, `../tables/table${tid}.xml`));
54
+ wsRels.push(rel(tRid, REL_TABLE, `../tables/table${tid}.xml`));
42
55
  }
43
- if (wsTableRels.length > 0) worksheetRels.push({
56
+ const drawingRelId = emitSheetCharts(s, sheet, idx, {
57
+ drawingParts,
58
+ drawingRels,
59
+ wsRels,
60
+ nextDrawing: () => ++drawingCounter,
61
+ nextChart: () => ++chartCounter,
62
+ losses
63
+ });
64
+ if (wsRels.length > 0) worksheetRels.push({
44
65
  sourcePart: wsPath,
45
- relationships: wsTableRels
66
+ relationships: wsRels
46
67
  });
47
68
  worksheetParts.push({
48
69
  path: wsPath,
49
- data: encoder.encode(worksheetXml(s.grid, tableRelIds)),
70
+ data: encoder.encode(worksheetXml(s.grid, tableRelIds, drawingRelId)),
50
71
  contentType: CT_WORKSHEET
51
72
  });
52
73
  workbookRels.push(rel(rid, REL_WORKSHEET, `worksheets/sheet${idx}.xml`));
@@ -55,7 +76,6 @@ function writeXlsx(sheet) {
55
76
  sheetId: idx,
56
77
  rid
57
78
  });
58
- recordSheetLosses(s, losses);
59
79
  });
60
80
  const sstRid = `rId${sheet.sheets.length + 1}`;
61
81
  const stylesRid = `rId${sheet.sheets.length + 2}`;
@@ -71,6 +91,7 @@ function writeXlsx(sheet) {
71
91
  },
72
92
  ...worksheetParts,
73
93
  ...tableParts,
94
+ ...drawingParts,
74
95
  {
75
96
  path: "xl/sharedStrings.xml",
76
97
  data: encoder.encode(sharedStringsXml(sheet.sharedStrings)),
@@ -83,10 +104,14 @@ function writeXlsx(sheet) {
83
104
  }
84
105
  ],
85
106
  rootRelationships: [rel("rId1", REL_OFFICE_DOCUMENT, "xl/workbook.xml")],
86
- partRelationships: [{
87
- sourcePart: "xl/workbook.xml",
88
- relationships: workbookRels
89
- }, ...worksheetRels]
107
+ partRelationships: [
108
+ {
109
+ sourcePart: "xl/workbook.xml",
110
+ relationships: workbookRels
111
+ },
112
+ ...worksheetRels,
113
+ ...drawingRels
114
+ ]
90
115
  }),
91
116
  losses
92
117
  };
@@ -100,19 +125,58 @@ function rel(id, type, target) {
100
125
  targetMode: "Internal"
101
126
  };
102
127
  }
103
- function recordSheetLosses(s, losses) {
104
- if (s.charts && s.charts.length > 0) losses.push({
105
- severity: "dropped",
106
- feature: FEATURES.charts,
107
- detail: `sheet "${s.name}": embedded charts not written back`
128
+ function emitSheetCharts(s, doc, idx, ctx) {
129
+ const refs = s.charts ?? [];
130
+ if (refs.length === 0 || !doc.chartData) return void 0;
131
+ const did = ctx.nextDrawing();
132
+ const drawingPath = `xl/drawings/drawing${did}.xml`;
133
+ const anchors = [];
134
+ const dRels = [];
135
+ refs.forEach((ref, ci) => {
136
+ const chart = doc.chartData?.get(ref.chartPartPath);
137
+ if (!chart) {
138
+ ctx.losses.push({
139
+ severity: "dropped",
140
+ feature: FEATURES.charts,
141
+ detail: `sheet "${s.name}": a chart frame had no resolved data and was dropped`
142
+ });
143
+ return;
144
+ }
145
+ const cid = ctx.nextChart();
146
+ ctx.drawingParts.push({
147
+ path: `xl/charts/chart${cid}.xml`,
148
+ data: encoder.encode(chartSpaceXml(chart)),
149
+ contentType: CT_CHART
150
+ });
151
+ const cRel = `rIdChart${ci + 1}`;
152
+ dRels.push(rel(cRel, REL_CHART, `../charts/chart${cid}.xml`));
153
+ anchors.push(chartAnchorXml(ref, cRel, ci));
154
+ });
155
+ if (anchors.length === 0) return void 0;
156
+ ctx.drawingParts.push({
157
+ path: drawingPath,
158
+ data: encoder.encode(`${XML_DECL}<xdr:wsDr xmlns:xdr="${XDR_NS}" xmlns:a="${A_NS}">${anchors.join("")}</xdr:wsDr>`),
159
+ contentType: CT_DRAWING
160
+ });
161
+ ctx.drawingRels.push({
162
+ sourcePart: drawingPath,
163
+ relationships: dRels
108
164
  });
165
+ const dRid = `rIdDraw${idx}`;
166
+ ctx.wsRels.push(rel(dRid, REL_DRAWING, `../drawings/drawing${did}.xml`));
167
+ return dRid;
168
+ }
169
+ function chartAnchorXml(ref, chartRelId, index) {
170
+ const cx = Math.round(ref.widthPt * EMU_PER_PT);
171
+ const cy = Math.round(ref.heightPt * EMU_PER_PT);
172
+ return `<xdr:oneCellAnchor><xdr:from><xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>${index * 16}</xdr:row><xdr:rowOff>0</xdr:rowOff></xdr:from><xdr:ext cx="${cx}" cy="${cy}"/><xdr:graphicFrame macro=""><xdr:nvGraphicFramePr><xdr:cNvPr id="${index + 2}" name="Chart ${index + 1}"/><xdr:cNvGraphicFramePr/></xdr:nvGraphicFramePr><xdr:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></xdr:xfrm><a:graphic><a:graphicData uri="${C_NS}"><c:chart xmlns:c="${C_NS}" xmlns:r="${R_NS}" r:id="${chartRelId}"/></a:graphicData></a:graphic></xdr:graphicFrame><xdr:clientData/></xdr:oneCellAnchor>`;
109
173
  }
110
174
  function workbookXml(sheets, definedNames, date1904) {
111
175
  const sheetTags = sheets.map((s) => `<sheet name="${escapeAttr(s.name)}" sheetId="${s.sheetId}" r:id="${s.rid}"/>`).join("");
112
176
  const definedNamesXml = definedNames.length > 0 ? `<definedNames>${definedNames.map((d) => `<definedName name="${escapeAttr(d.name)}"${d.localSheetId !== void 0 ? ` localSheetId="${d.localSheetId}"` : ""}>${escapeText(d.value)}</definedName>`).join("")}</definedNames>` : "";
113
177
  return XML_DECL + `<workbook xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (date1904 ? "<workbookPr date1904=\"1\"/>" : "") + `<sheets>${sheetTags}</sheets>` + definedNamesXml + "</workbook>";
114
178
  }
115
- function worksheetXml(grid, tableRelIds = []) {
179
+ function worksheetXml(grid, tableRelIds = [], drawingRelId) {
116
180
  const dimension = grid.maxRow >= 0 && grid.maxColumn >= 0 ? `<dimension ref="A1:${cellRef(grid.maxRow, grid.maxColumn)}"/>` : "<dimension ref=\"A1\"/>";
117
181
  const colsXml = grid.columns.length > 0 ? `<cols>${grid.columns.map((c) => `<col min="${c.min}" max="${c.max}" width="${c.widthChars}" customWidth="1"/>`).join("")}</cols>` : "";
118
182
  const heightByRow = /* @__PURE__ */ new Map();
@@ -136,7 +200,14 @@ function worksheetXml(grid, tableRelIds = []) {
136
200
  return `<row r="${r + 1}"${heightAttrs}>${cells.map(cellXml).join("")}</row>`;
137
201
  }).join("");
138
202
  const mergesXml = grid.merges.length > 0 ? `<mergeCells count="${grid.merges.length}">${grid.merges.map((m) => `<mergeCell ref="${cellRef(m.startRow, m.startColumn)}:${cellRef(m.endRow, m.endColumn)}"/>`).join("")}</mergeCells>` : "";
139
- return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
203
+ return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (drawingRelId ? `<drawing r:id="${drawingRelId}"/>` : "") + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
204
+ }
205
+ function sheetViewsXml(pane) {
206
+ if (!pane || pane.frozenRows <= 0 && pane.frozenCols <= 0) return "";
207
+ const { frozenRows, frozenCols } = pane;
208
+ const topLeftCell = cellRef(frozenRows, frozenCols);
209
+ const activePane = frozenRows > 0 && frozenCols > 0 ? "bottomRight" : frozenRows > 0 ? "bottomLeft" : "topRight";
210
+ return `<sheetViews><sheetView workbookViewId="0"><pane${(frozenCols > 0 ? ` xSplit="${frozenCols}"` : "") + (frozenRows > 0 ? ` ySplit="${frozenRows}"` : "") + ` topLeftCell="${topLeftCell}" activePane="${activePane}" state="frozen"`}/></sheetView></sheetViews>`;
140
211
  }
141
212
  function tableXml(t, id) {
142
213
  const ref = rangeRef(t.ref);