@particle-academy/holy-sheet 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -416,13 +416,15 @@ var MergedRegion = class {
416
416
 
417
417
  // src/workbook/sheet.ts
418
418
  var Sheet = class {
419
- constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0) {
419
+ constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0, rowHeights = /* @__PURE__ */ new Map(), print = null) {
420
420
  this.name = name;
421
421
  this.cells = cells;
422
422
  this.mergedRegions = mergedRegions;
423
423
  this.columnWidths = columnWidths;
424
424
  this.frozenRows = frozenRows;
425
425
  this.frozenCols = frozenCols;
426
+ this.rowHeights = rowHeights;
427
+ this.print = print;
426
428
  }
427
429
  /** rowNumber → (colLetter → Cell), sorted by ascending row number. */
428
430
  rows() {
@@ -493,6 +495,54 @@ var DateConverter = {
493
495
  }
494
496
  };
495
497
 
498
+ // src/exceptions.ts
499
+ var SchemaException = class _SchemaException extends Error {
500
+ constructor(errors, message) {
501
+ super(message ?? summarize(errors));
502
+ this.name = "SchemaException";
503
+ this.errors = errors;
504
+ Object.setPrototypeOf(this, _SchemaException.prototype);
505
+ }
506
+ static fromErrors(errors) {
507
+ return new _SchemaException(errors);
508
+ }
509
+ getErrors() {
510
+ return this.errors;
511
+ }
512
+ };
513
+ function summarize(errors) {
514
+ if (errors.length === 1) {
515
+ const e = errors[0];
516
+ return `[holy-sheet] schema invalid at ${e.path}: expected ${e.expected}, got ${e.got}`;
517
+ }
518
+ const first2 = errors[0];
519
+ const rest = errors.length - 1;
520
+ return `[holy-sheet] schema invalid at ${first2.path}: expected ${first2.expected}, got ${first2.got} (+${rest} more)`;
521
+ }
522
+ var UnsupportedFormatException = class _UnsupportedFormatException extends Error {
523
+ constructor(message, path = null, mimetype = null) {
524
+ super(message);
525
+ this.name = "UnsupportedFormatException";
526
+ this.path = path;
527
+ this.mimetype = mimetype;
528
+ Object.setPrototypeOf(this, _UnsupportedFormatException.prototype);
529
+ }
530
+ static notAZip(path = null) {
531
+ return new _UnsupportedFormatException(
532
+ `[holy-sheet] cannot read ${path ?? "this file"}: it is not a zip archive, and xlsx and ods both are`,
533
+ path
534
+ );
535
+ }
536
+ static unknownPackage(path, mimetype) {
537
+ const what = mimetype !== null && mimetype !== "" ? `its mimetype is ${mimetype}` : "it has neither xl/workbook.xml nor an OpenDocument mimetype";
538
+ return new _UnsupportedFormatException(
539
+ `[holy-sheet] cannot read ${path ?? "this file"}: ${what}. Supported: xlsx, ods`,
540
+ path,
541
+ mimetype !== "" ? mimetype : null
542
+ );
543
+ }
544
+ };
545
+
496
546
  // src/schema/theme.ts
497
547
  var Theme = class {
498
548
  constructor(key) {
@@ -570,7 +620,7 @@ var Normalizer = class {
570
620
  const columnFormat = columnFormats.get(c) ?? null;
571
621
  const rowBand = theme.dataFormat(r);
572
622
  const merged = rowBand !== null ? columnFormat ? columnFormat.mergeWith(rowBand) : rowBand : columnFormat;
573
- cells.set(address, this.buildCell(address, value, merged));
623
+ cells.set(address, this.buildCell(address, value, merged, name));
574
624
  });
575
625
  });
576
626
  if (sheet.totals && isPlainObject(sheet.totals) && Object.keys(sheet.totals).length > 0 && rows.length > 0 && columns.length > 0) {
@@ -594,7 +644,7 @@ var Normalizer = class {
594
644
  }
595
645
  }
596
646
  if (sheet.cells != null) {
597
- for (const [address, cell] of this.normalizeCellMap(sheet.cells)) {
647
+ for (const [address, cell] of this.normalizeCellMap(sheet.cells, name)) {
598
648
  const beneath = cells.get(address);
599
649
  cells.set(
600
650
  address,
@@ -615,17 +665,23 @@ var Normalizer = class {
615
665
  this.normalizeMerges(sheet.mergedRegions ?? []),
616
666
  this.normalizeWidths(sheet),
617
667
  toInt(sheet.frozenRows ?? 0),
618
- toInt(sheet.frozenCols ?? 0)
668
+ toInt(sheet.frozenCols ?? 0),
669
+ normalizeRowHeights(sheet.rowHeights),
670
+ normalizePrint(sheet.print)
619
671
  );
620
672
  }
621
- normalizeCellMap(map) {
673
+ normalizeCellMap(map, sheetName2) {
622
674
  const cells = /* @__PURE__ */ new Map();
623
675
  for (const [address, cellData] of Object.entries(map)) {
624
- if (!isPlainObject(cellData)) {
676
+ if (!isCellSpec(cellData)) {
625
677
  const [bare, formula] = promoteFormula(cellData);
626
678
  cells.set(
627
679
  address,
628
- new Cell(address, formula !== null ? null : this.coerceValue(bare, null), formula)
680
+ new Cell(
681
+ address,
682
+ formula !== null ? null : this.coerceValue(bare, null, address, sheetName2),
683
+ formula
684
+ )
629
685
  );
630
686
  continue;
631
687
  }
@@ -640,7 +696,7 @@ var Normalizer = class {
640
696
  address,
641
697
  new Cell(
642
698
  address,
643
- this.coerceValue(rawValue, format),
699
+ this.coerceValue(rawValue, format, address, sheetName2),
644
700
  cellData?.formula ?? null,
645
701
  format,
646
702
  comment,
@@ -721,8 +777,8 @@ var Normalizer = class {
721
777
  return null;
722
778
  }
723
779
  }
724
- buildCell(address, value, columnFormat) {
725
- if (isPlainObject(value)) {
780
+ buildCell(address, value, columnFormat, sheetName2) {
781
+ if (isCellSpec(value)) {
726
782
  const cellFormat = isPlainObject(value.format) ? CellFormat.fromInput(value.format) : null;
727
783
  const merged = columnFormat ? columnFormat.mergeWith(cellFormat) : cellFormat;
728
784
  const comment = isPlainObject(value.comment) ? new CellComment(
@@ -733,7 +789,7 @@ var Normalizer = class {
733
789
  const rawValue = value.value ?? null;
734
790
  return new Cell(
735
791
  address,
736
- this.coerceValue(rawValue, merged),
792
+ this.coerceValue(rawValue, merged, address, sheetName2),
737
793
  value.formula ?? null,
738
794
  merged,
739
795
  comment,
@@ -743,12 +799,12 @@ var Normalizer = class {
743
799
  const [bare, formula] = promoteFormula(value);
744
800
  return new Cell(
745
801
  address,
746
- formula !== null ? null : this.coerceValue(bare, columnFormat),
802
+ formula !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
747
803
  formula,
748
804
  columnFormat
749
805
  );
750
806
  }
751
- coerceValue(value, format) {
807
+ coerceValue(value, format, address = "?", sheetName2) {
752
808
  if (value === null || value === void 0) return null;
753
809
  const df = format?.displayFormat;
754
810
  if (df === "date" || df === "datetime") {
@@ -762,12 +818,36 @@ var Normalizer = class {
762
818
  if (value instanceof Date) {
763
819
  return DateConverter.toSerial(value, true);
764
820
  }
765
- if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
821
+ if (typeof value === "bigint") {
822
+ return String(value);
823
+ }
824
+ if (typeof value === "object" && !Array.isArray(value) && hasOwnToString(value)) {
766
825
  return String(value);
767
826
  }
827
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
828
+ const where = sheetName2 === void 0 ? address : `${sheetName2}!${address}`;
829
+ const got = typeOf(value);
830
+ throw SchemaException.fromErrors([
831
+ {
832
+ path: where,
833
+ expected: "a string, number, bool, null, Date, or an object with its own toString",
834
+ got,
835
+ value,
836
+ hint: Array.isArray(value) ? 'This cell holds an array. The usual cause is a `rows` entry nested one level too deep: `rows: [[["x"]]]` makes cell A1 an array, where `rows: [["x"]]` makes it the string "x". For a cell with formatting use the object form: {value: "x", format: {...}}.' : "Give the cell a scalar or a Date, or define toString() on the object so the conversion is yours rather than implicit."
837
+ }
838
+ ]);
839
+ }
768
840
  return value;
769
841
  }
770
842
  };
843
+ function isCellSpec(value) {
844
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
845
+ return Object.getPrototypeOf(value) === Object.prototype;
846
+ }
847
+ function hasOwnToString(value) {
848
+ const fn = value.toString;
849
+ return typeof fn === "function" && fn !== Object.prototype.toString;
850
+ }
771
851
  function promoteFormula(value) {
772
852
  if (typeof value === "string" && value.length > 1 && value[0] === "=") {
773
853
  return [null, value.slice(1)];
@@ -778,6 +858,34 @@ function toInt(v) {
778
858
  const n = Math.trunc(Number(v));
779
859
  return Number.isFinite(n) ? n : 0;
780
860
  }
861
+ function phpNumeric(v) {
862
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
863
+ return isNumericString(v) ? numericStringToNumber(v) : null;
864
+ }
865
+ function normalizeRowHeights(map) {
866
+ const out = /* @__PURE__ */ new Map();
867
+ if (!isPlainObject(map) && !Array.isArray(map)) return out;
868
+ for (const [row, px] of Object.entries(map)) {
869
+ const n = phpNumeric(px);
870
+ if (/^[1-9]\d{0,6}$/.test(row) && n !== null && n >= 0) out.set(parseInt(row, 10), n);
871
+ }
872
+ return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
873
+ }
874
+ function normalizePrint(print) {
875
+ if (!isPlainObject(print) && !Array.isArray(print)) return null;
876
+ const p = print;
877
+ const orientation = p.orientation === "portrait" || p.orientation === "landscape" ? p.orientation : null;
878
+ const fitToWidth = p.fitToWidth === true;
879
+ let footer = null;
880
+ if (isPlainObject(p.footer) || Array.isArray(p.footer)) {
881
+ const f = p.footer;
882
+ const slot = (k) => typeof f[k] === "string" ? f[k] : "";
883
+ footer = { left: slot("left"), center: slot("center"), right: slot("right") };
884
+ if (footer.left === "" && footer.center === "" && footer.right === "") footer = null;
885
+ }
886
+ if (orientation === null && !fitToWidth && footer === null) return null;
887
+ return { orientation, fitToWidth, footer };
888
+ }
781
889
 
782
890
  // src/schema/formula-linter.ts
783
891
  var ERR_VALUE = "#VALUE!";
@@ -1299,54 +1407,6 @@ function phpRound(x, n) {
1299
1407
  return Math.sign(x) * Math.round(Math.abs(x) * factor) / factor;
1300
1408
  }
1301
1409
 
1302
- // src/exceptions.ts
1303
- var SchemaException = class _SchemaException extends Error {
1304
- constructor(errors, message) {
1305
- super(message ?? summarize(errors));
1306
- this.name = "SchemaException";
1307
- this.errors = errors;
1308
- Object.setPrototypeOf(this, _SchemaException.prototype);
1309
- }
1310
- static fromErrors(errors) {
1311
- return new _SchemaException(errors);
1312
- }
1313
- getErrors() {
1314
- return this.errors;
1315
- }
1316
- };
1317
- function summarize(errors) {
1318
- if (errors.length === 1) {
1319
- const e = errors[0];
1320
- return `[holy-sheet] schema invalid at ${e.path}: expected ${e.expected}, got ${e.got}`;
1321
- }
1322
- const first2 = errors[0];
1323
- const rest = errors.length - 1;
1324
- return `[holy-sheet] schema invalid at ${first2.path}: expected ${first2.expected}, got ${first2.got} (+${rest} more)`;
1325
- }
1326
- var UnsupportedFormatException = class _UnsupportedFormatException extends Error {
1327
- constructor(message, path = null, mimetype = null) {
1328
- super(message);
1329
- this.name = "UnsupportedFormatException";
1330
- this.path = path;
1331
- this.mimetype = mimetype;
1332
- Object.setPrototypeOf(this, _UnsupportedFormatException.prototype);
1333
- }
1334
- static notAZip(path = null) {
1335
- return new _UnsupportedFormatException(
1336
- `[holy-sheet] cannot read ${path ?? "this file"}: it is not a zip archive, and xlsx and ods both are`,
1337
- path
1338
- );
1339
- }
1340
- static unknownPackage(path, mimetype) {
1341
- const what = mimetype !== null && mimetype !== "" ? `its mimetype is ${mimetype}` : "it has neither xl/workbook.xml nor an OpenDocument mimetype";
1342
- return new _UnsupportedFormatException(
1343
- `[holy-sheet] cannot read ${path ?? "this file"}: ${what}. Supported: xlsx, ods`,
1344
- path,
1345
- mimetype !== "" ? mimetype : null
1346
- );
1347
- }
1348
- };
1349
-
1350
1410
  // src/schema/repairer.ts
1351
1411
  var VALID_THEMES = ["default", "minimal", "plain", "business"];
1352
1412
  var ISO_DATE2 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/;
@@ -1593,7 +1653,21 @@ var Validator = class {
1593
1653
  "Each row is an array of cell values, in column order."
1594
1654
  )
1595
1655
  );
1656
+ return;
1596
1657
  }
1658
+ row.forEach((cell, k) => {
1659
+ if (Array.isArray(cell)) {
1660
+ errors.push(
1661
+ error(
1662
+ `${path}.rows[${j}][${k}]`,
1663
+ "a cell value or a cell object",
1664
+ "array",
1665
+ cell,
1666
+ 'This cell is an array, which usually means the row is nested one level too deep: `rows: [[["x"]]]` makes cell A1 an array, where `rows: [["x"]]` makes it "x". For a cell with formatting use the object form: {value: "x", format: {...}}.'
1667
+ )
1668
+ );
1669
+ }
1670
+ });
1597
1671
  });
1598
1672
  }
1599
1673
  }
@@ -1659,6 +1733,95 @@ var Validator = class {
1659
1733
  }
1660
1734
  }
1661
1735
  }
1736
+ if (sheet.rowHeights != null) {
1737
+ if (!isPlainObject(sheet.rowHeights) && !Array.isArray(sheet.rowHeights)) {
1738
+ errors.push(
1739
+ error(
1740
+ `${path}.rowHeights`,
1741
+ "object keyed by 1-based row number",
1742
+ typeOf(sheet.rowHeights),
1743
+ sheet.rowHeights,
1744
+ 'Row heights map a row NUMBER to pixels: {"1": 32, "4": 28}.'
1745
+ )
1746
+ );
1747
+ } else {
1748
+ for (const [row, px] of Object.entries(sheet.rowHeights)) {
1749
+ const n = typeof px === "number" ? px : isNumericString(px) ? numericStringToNumber(px) : null;
1750
+ if (!/^[1-9]\d{0,6}$/.test(row)) {
1751
+ errors.push(error(
1752
+ `${path}.rowHeights.${row}`,
1753
+ "a 1-based row number",
1754
+ typeOf(row),
1755
+ row,
1756
+ 'Keys are row numbers as Excel shows them: "1" is the first row.'
1757
+ ));
1758
+ } else if (n === null || !Number.isFinite(n) || n < 0) {
1759
+ errors.push(error(
1760
+ `${path}.rowHeights.${row}`,
1761
+ "a non-negative number of pixels",
1762
+ typeOf(px),
1763
+ px,
1764
+ "A height is a number of pixels, like 28 (20 is a default row)."
1765
+ ));
1766
+ }
1767
+ }
1768
+ }
1769
+ }
1770
+ if (sheet.print != null) {
1771
+ const print = sheet.print;
1772
+ if (!isPlainObject(print) && !Array.isArray(print)) {
1773
+ errors.push(error(
1774
+ `${path}.print`,
1775
+ "object {orientation?, fitToWidth?, footer?}",
1776
+ typeOf(print),
1777
+ print,
1778
+ 'Print setup: {"orientation": "landscape", "fitToWidth": true, "footer": {"right": "Page {page} of {pages}"}}.'
1779
+ ));
1780
+ } else {
1781
+ if (print.orientation != null && print.orientation !== "portrait" && print.orientation !== "landscape") {
1782
+ errors.push(error(
1783
+ `${path}.print.orientation`,
1784
+ "one of: portrait, landscape",
1785
+ typeOf(print.orientation),
1786
+ print.orientation,
1787
+ "Use landscape for a sheet wider than it is tall."
1788
+ ));
1789
+ }
1790
+ if (print.fitToWidth != null && typeof print.fitToWidth !== "boolean") {
1791
+ errors.push(error(
1792
+ `${path}.print.fitToWidth`,
1793
+ "boolean",
1794
+ typeOf(print.fitToWidth),
1795
+ print.fitToWidth,
1796
+ "true scales the sheet to one page wide when printed."
1797
+ ));
1798
+ }
1799
+ if (print.footer != null) {
1800
+ if (!isPlainObject(print.footer) && !Array.isArray(print.footer)) {
1801
+ errors.push(error(
1802
+ `${path}.print.footer`,
1803
+ "object {left?, center?, right?}",
1804
+ typeOf(print.footer),
1805
+ print.footer,
1806
+ "A footer has up to three slots of text."
1807
+ ));
1808
+ } else {
1809
+ const footer = print.footer;
1810
+ for (const slot of ["left", "center", "right"]) {
1811
+ if (footer[slot] != null && typeof footer[slot] !== "string") {
1812
+ errors.push(error(
1813
+ `${path}.print.footer.${slot}`,
1814
+ "string",
1815
+ typeOf(footer[slot]),
1816
+ footer[slot],
1817
+ "Footer text; {page} and {pages} print the page number and count."
1818
+ ));
1819
+ }
1820
+ }
1821
+ }
1822
+ }
1823
+ }
1824
+ }
1662
1825
  return errors;
1663
1826
  }
1664
1827
  validateColumn(col, path) {
@@ -2748,6 +2911,22 @@ function sheetToSchema(sheet) {
2748
2911
  }
2749
2912
  if (sheet.frozenRows > 0) out["frozenRows"] = sheet.frozenRows;
2750
2913
  if (sheet.frozenCols > 0) out["frozenCols"] = sheet.frozenCols;
2914
+ if (sheet.rowHeights.size > 0) {
2915
+ const heights = {};
2916
+ for (const [k, v] of sheet.rowHeights) heights[k] = v;
2917
+ out["rowHeights"] = heights;
2918
+ }
2919
+ if (sheet.print !== null) {
2920
+ const print = {};
2921
+ if (sheet.print.orientation !== null) print["orientation"] = sheet.print.orientation;
2922
+ if (sheet.print.fitToWidth) print["fitToWidth"] = true;
2923
+ if (sheet.print.footer !== null) {
2924
+ const footer = {};
2925
+ for (const k of ["left", "center", "right"]) if (sheet.print.footer[k] !== "") footer[k] = sheet.print.footer[k];
2926
+ print["footer"] = footer;
2927
+ }
2928
+ out["print"] = print;
2929
+ }
2751
2930
  return out;
2752
2931
  }
2753
2932
  function cellToSchema(cell) {
@@ -3370,9 +3549,62 @@ var WorksheetParser = {
3370
3549
  const merges = parseMerges(xml);
3371
3550
  const columnWidths = parseColumnWidths(xml);
3372
3551
  const [frozenRows, frozenCols] = parseFrozen(xml);
3373
- return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols);
3552
+ return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols, parseRowHeights(xml), parsePrint(xml));
3374
3553
  }
3375
3554
  };
3555
+ function parseRowHeights(xml) {
3556
+ const out = /* @__PURE__ */ new Map();
3557
+ const sheetData = el(xml, "sheetData");
3558
+ if (!sheetData) return out;
3559
+ for (const row of els(sheetData, "row")) {
3560
+ const r = parseInt(at(row, "r") ?? "0", 10);
3561
+ const custom = at(row, "customHeight");
3562
+ const ht = at(row, "ht");
3563
+ if (r > 0 && ht !== void 0 && (custom === "1" || custom === "true")) {
3564
+ out.set(r, Math.round(parseFloat(ht) / 0.75 * 100) / 100);
3565
+ }
3566
+ }
3567
+ return out;
3568
+ }
3569
+ function parsePrint(xml) {
3570
+ const fitToPage = ["1", "true"].includes(at(el(el(xml, "sheetPr"), "pageSetUpPr"), "fitToPage") ?? "");
3571
+ const setup = el(xml, "pageSetup");
3572
+ const o = at(setup, "orientation");
3573
+ const orientation = o === "portrait" || o === "landscape" ? o : null;
3574
+ const fitToWidth = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && ["0", ""].includes(at(setup, "fitToHeight") ?? "");
3575
+ const oddFooter = el(el(xml, "headerFooter"), "oddFooter");
3576
+ const footer = oddFooter !== void 0 ? parseFooterCodes(oddFooter.text) : null;
3577
+ if (orientation === null && !fitToWidth && footer === null) return null;
3578
+ return { orientation, fitToWidth, footer };
3579
+ }
3580
+ function parseFooterCodes(codes) {
3581
+ const slots = { left: "", center: "", right: "" };
3582
+ let current = "center";
3583
+ const len = codes.length;
3584
+ for (let i = 0; i < len; i++) {
3585
+ const ch = codes[i];
3586
+ if (ch !== "&" || i + 1 >= len) {
3587
+ slots[current] += ch;
3588
+ continue;
3589
+ }
3590
+ const next = codes[++i];
3591
+ if (next === "&") slots[current] += "&";
3592
+ else if (next === "L") current = "left";
3593
+ else if (next === "C") current = "center";
3594
+ else if (next === "R") current = "right";
3595
+ else if (next === "P") slots[current] += "{page}";
3596
+ else if (next === "N") slots[current] += "{pages}";
3597
+ else if (next === '"') {
3598
+ const close = codes.indexOf('"', i + 1);
3599
+ i = close === -1 ? len : close;
3600
+ } else if (/[0-9]/.test(next)) {
3601
+ while (i + 1 < len && /[0-9]/.test(codes[i + 1])) i++;
3602
+ } else if (next === "K") {
3603
+ i = Math.min(len - 1, i + 6);
3604
+ }
3605
+ }
3606
+ return slots.left === "" && slots.center === "" && slots.right === "" ? null : slots;
3607
+ }
3376
3608
  function parseCells(xml, stylesIndex, comments, sharedStrings) {
3377
3609
  const cells = /* @__PURE__ */ new Map();
3378
3610
  const sheetData = el(xml, "sheetData");
@@ -3748,6 +3980,19 @@ function hexToArgb(hex2) {
3748
3980
 
3749
3981
  // src/writer/xlsx-writer.ts
3750
3982
  var encoder2 = new TextEncoder();
3983
+ function footerCodes(footer) {
3984
+ let codes = "";
3985
+ for (const [slot, code] of [["left", "&L"], ["center", "&C"], ["right", "&R"]]) {
3986
+ if (footer[slot] !== "") {
3987
+ codes += code + footer[slot].replace(/&/g, "&&").replace(/\{pages\}/g, "&N").replace(/\{page\}/g, "&P");
3988
+ }
3989
+ }
3990
+ return codes;
3991
+ }
3992
+ function decimal(value) {
3993
+ const out = value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
3994
+ return out === "" || out === "-0" ? "0" : out;
3995
+ }
3751
3996
  var XlsxWriter = class {
3752
3997
  toBytes(workbook) {
3753
3998
  const styles = new StylesRegistry();
@@ -3836,14 +4081,20 @@ var XlsxWriter = class {
3836
4081
  }
3837
4082
  colsXml += "</cols>";
3838
4083
  }
4084
+ const rows = sheet.rows();
4085
+ for (const rowIndex of sheet.rowHeights.keys()) {
4086
+ if (!rows.has(rowIndex)) rows.set(rowIndex, /* @__PURE__ */ new Map());
4087
+ }
3839
4088
  let rowsXml = "";
3840
- for (const [rowIndex, row] of sheet.rows()) {
4089
+ for (const [rowIndex, row] of [...rows.entries()].sort((a, b) => a[0] - b[0])) {
3841
4090
  let cellsXml = "";
3842
4091
  const sortedCols = [...row.keys()].sort();
3843
4092
  for (const col of sortedCols) {
3844
4093
  cellsXml += this.cellXml(row.get(col), styles);
3845
4094
  }
3846
- rowsXml += `<row r="${rowIndex}">${cellsXml}</row>`;
4095
+ const height = sheet.rowHeights.get(rowIndex);
4096
+ const ht = height !== void 0 ? ` ht="${decimal(height * 0.75)}" customHeight="1"` : "";
4097
+ rowsXml += cellsXml === "" ? `<row r="${rowIndex}"${ht}/>` : `<row r="${rowIndex}"${ht}>${cellsXml}</row>`;
3847
4098
  }
3848
4099
  let mergesXml = "";
3849
4100
  if (sheet.mergedRegions.length > 0) {
@@ -3852,7 +4103,14 @@ var XlsxWriter = class {
3852
4103
  mergesXml += "</mergeCells>";
3853
4104
  }
3854
4105
  const legacyDrawing = sheet.hasComments() ? '<legacyDrawing r:id="rId1"/>' : "";
3855
- return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + sheetViewsXml + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + legacyDrawing + "</worksheet>";
4106
+ const print = sheet.print;
4107
+ const sheetPrXml = print?.fitToWidth ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
4108
+ let pageSetupXml = "";
4109
+ if (print !== null && (print.orientation !== null || print.fitToWidth)) {
4110
+ pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + (print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
4111
+ }
4112
+ const headerFooterXml = print?.footer ? `<headerFooter><oddFooter>${this.escape(footerCodes(print.footer))}</oddFooter></headerFooter>` : "";
4113
+ return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + sheetPrXml + sheetViewsXml + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + pageSetupXml + headerFooterXml + legacyDrawing + "</worksheet>";
3856
4114
  }
3857
4115
  cellXml(cell, styles) {
3858
4116
  const ref = cell.address;
@@ -4969,6 +5227,31 @@ var holy_sheet_schema_default = {
4969
5227
  type: "integer",
4970
5228
  minimum: 0,
4971
5229
  description: "How many columns stay visible while the sheet scrolls sideways. Set to 1 when the first column is the label for the row."
5230
+ },
5231
+ rowHeights: {
5232
+ type: "object",
5233
+ description: 'Row heights in pixels, keyed by 1-based row NUMBER as Excel shows it: {"1": 32, "4": 28}. A default row is 20; give a title row or a header row room (28-36) instead of packing text against its fill.',
5234
+ propertyNames: { pattern: "^[1-9][0-9]*$" },
5235
+ additionalProperties: { type: "number", minimum: 0 }
5236
+ },
5237
+ print: {
5238
+ type: "object",
5239
+ additionalProperties: false,
5240
+ description: "How the sheet prints. A sheet wider than a page splits its columns across pages unless fitToWidth is set.",
5241
+ properties: {
5242
+ orientation: { type: "string", enum: ["portrait", "landscape"] },
5243
+ fitToWidth: { type: "boolean", description: "Scale the sheet to one page wide (as many pages tall as it needs)." },
5244
+ footer: {
5245
+ type: "object",
5246
+ additionalProperties: false,
5247
+ description: 'A printed footer: up to three slots. {page} and {pages} print the page number and count: {"left": "Q3 Revenue Review", "right": "Page {page} of {pages}"}.',
5248
+ properties: {
5249
+ left: { type: "string" },
5250
+ center: { type: "string" },
5251
+ right: { type: "string" }
5252
+ }
5253
+ }
5254
+ }
4972
5255
  }
4973
5256
  }
4974
5257
  },
@@ -5177,7 +5460,7 @@ var holy_sheet_schema_default = {
5177
5460
  };
5178
5461
 
5179
5462
  // src/agent.ts
5180
- var VERSION = "2.5.0";
5463
+ var VERSION = "2.7.0";
5181
5464
  function toU8(input) {
5182
5465
  return input instanceof Uint8Array ? input : new Uint8Array(input);
5183
5466
  }