reamkit 1.32.0 → 1.33.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 (157) hide show
  1. package/README.md +5 -4
  2. package/dist/esm/core/bidi/algorithm.js +2 -2
  3. package/dist/esm/core/bidi/char-types.js +1 -1
  4. package/dist/esm/core/bidi/index.js +3 -3
  5. package/dist/esm/core/bytes.js +1 -1
  6. package/dist/esm/core/converter/facade.js +3 -2
  7. package/dist/esm/core/converter/ream.js +12 -5
  8. package/dist/esm/core/crypto/asn1.js +2 -2
  9. package/dist/esm/core/crypto/cms.js +5 -3
  10. package/dist/esm/core/crypto/index.js +2 -1
  11. package/dist/esm/core/crypto/offcrypto.js +64 -19
  12. package/dist/esm/core/crypto/primitives.js +16 -16
  13. package/dist/esm/core/document-model/index.d.ts +1 -1
  14. package/dist/esm/core/document-model/types.d.ts +174 -2
  15. package/dist/esm/core/drawingml/chart-geometry.d.ts +45 -17
  16. package/dist/esm/core/drawingml/chart-geometry.js +1090 -257
  17. package/dist/esm/core/drawingml/chart-parser.d.ts +24 -1
  18. package/dist/esm/core/drawingml/chart-parser.js +334 -41
  19. package/dist/esm/core/drawingml/chart-text.d.ts +41 -0
  20. package/dist/esm/core/drawingml/chart-text.js +93 -0
  21. package/dist/esm/core/drawingml/colors.js +5 -5
  22. package/dist/esm/core/drawingml/diagram/layout-engine.js +1 -4
  23. package/dist/esm/core/drawingml/diagram/run.js +2 -1
  24. package/dist/esm/core/drawingml/preset-geometry.js +4 -6
  25. package/dist/esm/core/drawingml/shape-render.js +1 -1
  26. package/dist/esm/core/drawingml/text-warp.js +4 -16
  27. package/dist/esm/core/drawingml/theme-parser.js +14 -8
  28. package/dist/esm/core/font/arabic-joining.js +2 -2
  29. package/dist/esm/core/font/binary-reader.js +2 -0
  30. package/dist/esm/core/font/font-registry.js +1 -0
  31. package/dist/esm/core/font/ligatures.js +1 -1
  32. package/dist/esm/core/font/opentype-layout.js +11 -5
  33. package/dist/esm/core/font/ttf-build.js +6 -10
  34. package/dist/esm/core/font/ttf-parser.js +2 -1
  35. package/dist/esm/core/font/ttf-subset.js +8 -8
  36. package/dist/esm/core/fonts/families.js +1 -1
  37. package/dist/esm/core/fonts/remote-fonts.js +4 -4
  38. package/dist/esm/core/hyphenation/index.js +1 -1
  39. package/dist/esm/core/images.js +8 -8
  40. package/dist/esm/core/ir/loss.js +1 -0
  41. package/dist/esm/core/line-breaker/cjk.js +1 -1
  42. package/dist/esm/core/line-breaker/index.js +4 -3
  43. package/dist/esm/core/links.js +1 -1
  44. package/dist/esm/core/lzw.js +1 -1
  45. package/dist/esm/core/metafile/blit.js +1 -1
  46. package/dist/esm/core/metafile/emf.js +5 -7
  47. package/dist/esm/core/metafile/picture.js +1 -1
  48. package/dist/esm/core/metafile/symbol-fonts.js +7 -7
  49. package/dist/esm/core/metafile/wmf.js +5 -7
  50. package/dist/esm/core/number-format.d.ts +40 -15
  51. package/dist/esm/core/number-format.js +284 -79
  52. package/dist/esm/core/numbering/index.js +3 -2
  53. package/dist/esm/core/ole/cfb.js +4 -4
  54. package/dist/esm/core/ole/escher-blip.js +1 -1
  55. package/dist/esm/core/opc/index.js +5 -4
  56. package/dist/esm/core/opc/package.js +3 -1
  57. package/dist/esm/core/opc/tag-scan.d.ts +15 -0
  58. package/dist/esm/core/opc/tag-scan.js +41 -0
  59. package/dist/esm/core/opc/xml-entities.js +40 -3
  60. package/dist/esm/core/outline.js +1 -1
  61. package/dist/esm/core/png-encode.js +3 -3
  62. package/dist/esm/core/po-helpers.js +1 -1
  63. package/dist/esm/core/spreadsheet-model/types.d.ts +6 -0
  64. package/dist/esm/core/style-cascade/index.js +4 -3
  65. package/dist/esm/core/tiff.js +3 -1
  66. package/dist/esm/excel/activex-parser.js +1 -1
  67. package/dist/esm/excel/conditional-format.js +7 -7
  68. package/dist/esm/excel/formula/functions.js +45 -18
  69. package/dist/esm/excel/formula/index.js +3 -3
  70. package/dist/esm/excel/formula/lexer.js +2 -2
  71. package/dist/esm/excel/formula/parser.js +4 -3
  72. package/dist/esm/excel/formula/value.js +1 -1
  73. package/dist/esm/excel/index.js +7 -6
  74. package/dist/esm/excel/print-model.js +18 -7
  75. package/dist/esm/excel/printer-settings.js +6 -7
  76. package/dist/esm/excel/rich-value.js +1 -1
  77. package/dist/esm/excel/sheet-shape-parser.js +4 -2
  78. package/dist/esm/excel/sheet-to-flow.js +13 -11
  79. package/dist/esm/excel/styles-parser.js +5 -3
  80. package/dist/esm/excel/tint.js +5 -5
  81. package/dist/esm/excel/vml-drawing.js +3 -3
  82. package/dist/esm/excel/worksheet-parser.js +5 -5
  83. package/dist/esm/excel/xls/biff-chart.js +1 -3
  84. package/dist/esm/excel/xls/biff-reader.js +10 -11
  85. package/dist/esm/excel/xls/xls-reader.js +1 -1
  86. package/dist/esm/excel/xlsx-reader.js +56 -9
  87. package/dist/esm/excel/xlsx-to-pdf.js +1 -1
  88. package/dist/esm/excel/xlsx-writer.js +2 -2
  89. package/dist/esm/html/html-writer.js +18 -10
  90. package/dist/esm/layout/line-paint.js +1 -1
  91. package/dist/esm/layout/math-layout.js +5 -8
  92. package/dist/esm/layout/styled-layout.js +104 -32
  93. package/dist/esm/layout/turned-section.js +2 -2
  94. package/dist/esm/markdown/markdown-writer.js +59 -8
  95. package/dist/esm/pdf/encryption.js +9 -9
  96. package/dist/esm/pdf/icc-profile.js +2 -2
  97. package/dist/esm/pdf/index.js +3 -2
  98. package/dist/esm/pdf/objects.js +7 -0
  99. package/dist/esm/pdf/serialize.js +1 -1
  100. package/dist/esm/pdf/struct-tree.js +3 -1
  101. package/dist/esm/pdf/styled-page-emitter.js +23 -15
  102. package/dist/esm/pdf/styled-page-renderer.js +9 -5
  103. package/dist/esm/pdf-reader/annot-draw.js +5 -5
  104. package/dist/esm/pdf-reader/annots.js +3 -1
  105. package/dist/esm/pdf-reader/ccitt.js +2 -1
  106. package/dist/esm/pdf-reader/cff-outline.js +1 -4
  107. package/dist/esm/pdf-reader/content.js +13 -15
  108. package/dist/esm/pdf-reader/decrypt.js +5 -4
  109. package/dist/esm/pdf-reader/document.js +6 -2
  110. package/dist/esm/pdf-reader/encodings.js +1 -1
  111. package/dist/esm/pdf-reader/flow-build.js +4 -6
  112. package/dist/esm/pdf-reader/font.js +4 -4
  113. package/dist/esm/pdf-reader/function.js +8 -7
  114. package/dist/esm/pdf-reader/icc.js +1 -1
  115. package/dist/esm/pdf-reader/images.js +1 -1
  116. package/dist/esm/pdf-reader/jbig2.js +36 -38
  117. package/dist/esm/pdf-reader/jpeg.js +14 -13
  118. package/dist/esm/pdf-reader/layout.js +13 -10
  119. package/dist/esm/pdf-reader/lexer.js +2 -1
  120. package/dist/esm/pdf-reader/math-rows.js +2 -2
  121. package/dist/esm/pdf-reader/optional-content.js +1 -1
  122. package/dist/esm/pdf-reader/page-numbers.js +1 -1
  123. package/dist/esm/pdf-reader/pattern-tint.js +1 -2
  124. package/dist/esm/pdf-reader/reader.js +2 -2
  125. package/dist/esm/pdf-reader/struct-tree.js +1 -1
  126. package/dist/esm/pdf-reader/tagged.js +3 -2
  127. package/dist/esm/pdf-reader/text.js +2 -2
  128. package/dist/esm/pdf-reader/type1-outline.js +1 -3
  129. package/dist/esm/pptx/embedded-fonts.js +3 -5
  130. package/dist/esm/pptx/placeholder-cascade.js +1 -1
  131. package/dist/esm/pptx/ppt/ppt-reader.js +5 -4
  132. package/dist/esm/pptx/ppt/ppt-text.js +9 -10
  133. package/dist/esm/pptx/pptx-reader.js +6 -5
  134. package/dist/esm/pptx/preset-table-styles.js +1 -1
  135. package/dist/esm/pptx/slide-parser.js +6 -4
  136. package/dist/esm/pptx/sp-helpers.js +1 -1
  137. package/dist/esm/pptx/table-style.js +2 -1
  138. package/dist/esm/svg/svg-writer.js +1 -1
  139. package/dist/esm/word/doc/doc-reader.js +2 -2
  140. package/dist/esm/word/doc/doc-text.js +20 -22
  141. package/dist/esm/word/document-parser.js +27 -22
  142. package/dist/esm/word/docx-reader.js +15 -10
  143. package/dist/esm/word/docx-to-pdf.js +6 -6
  144. package/dist/esm/word/docx-writer.js +10 -6
  145. package/dist/esm/word/drawing-parser.js +18 -18
  146. package/dist/esm/word/font-table.js +90 -11
  147. package/dist/esm/word/index.js +6 -5
  148. package/dist/esm/word/numbering-parser.js +5 -4
  149. package/dist/esm/word/omml-parser.js +1 -1
  150. package/dist/esm/word/paragraph-properties.js +11 -10
  151. package/dist/esm/word/run-properties.js +4 -4
  152. package/dist/esm/word/settings-parser.js +2 -1
  153. package/dist/esm/word/shading.js +1 -1
  154. package/dist/esm/word/styles-parser.js +5 -4
  155. package/dist/esm/word/table-parser.js +4 -4
  156. package/dist/esm/word/theme-fonts.js +1 -1
  157. package/package.json +10 -4
@@ -1,6 +1,6 @@
1
1
  import { INDEXED_COLORS } from "./indexed-colors.js";
2
2
  //#region src/core/number-format.ts
3
- var BUILTIN = new Map([
3
+ var BUILTIN = /* @__PURE__ */ new Map([
4
4
  [0, "General"],
5
5
  [1, "0"],
6
6
  [2, "0.00"],
@@ -22,7 +22,7 @@ var BUILTIN = new Map([
22
22
  [48, "##0.0E+0"],
23
23
  [49, "@"]
24
24
  ]);
25
- var BUILTIN_DATE_FORMATS = new Map([
25
+ var BUILTIN_DATE_FORMATS = /* @__PURE__ */ new Map([
26
26
  [14, "m/d/yyyy"],
27
27
  [15, "d-mmm-yy"],
28
28
  [16, "d-mmm"],
@@ -85,7 +85,7 @@ function numberFormatColorHex(rawValue, numFmtId, customFormats) {
85
85
  if (idx < 0) return void 0;
86
86
  return colorOfSection(sections[idx] ?? sections[0]);
87
87
  }
88
- var FORMAT_COLORS = new Map([
88
+ var FORMAT_COLORS = /* @__PURE__ */ new Map([
89
89
  ["black", "000000"],
90
90
  ["blue", "0000FF"],
91
91
  ["cyan", "00FFFF"],
@@ -95,8 +95,17 @@ var FORMAT_COLORS = new Map([
95
95
  ["white", "FFFFFF"],
96
96
  ["yellow", "FFFF00"]
97
97
  ]);
98
+ var BRACKET_CODE = /\[([^\]]*)\]/g;
99
+ function bracketCodes(format) {
100
+ return format.slice(0, format.lastIndexOf("]") + 1).matchAll(BRACKET_CODE);
101
+ }
102
+ /** The format with each `[…]` code replaced by what `fn` makes of its inside. */
103
+ function replaceBracketCodes(format, fn) {
104
+ const end = format.lastIndexOf("]") + 1;
105
+ return format.slice(0, end).replace(BRACKET_CODE, (_, body) => fn(body)) + format.slice(end);
106
+ }
98
107
  function colorOfSection(section) {
99
- for (const m of section.matchAll(/\[([^\]]*)\]/g)) {
108
+ for (const m of bracketCodes(section)) {
100
109
  const body = m[1].trim().toLowerCase();
101
110
  const named = FORMAT_COLORS.get(body);
102
111
  if (named !== void 0) return named;
@@ -251,7 +260,7 @@ function hasElapsedToken(code) {
251
260
  return /\[(?:h+|m+|s+)\]/i.test(code.replace(/"[^"]*"/g, ""));
252
261
  }
253
262
  function isDateFormat(code) {
254
- const cleaned = code.replace(/"[^"]*"/g, "").replace(/\[[^\]]*\]/g, "");
263
+ const cleaned = replaceBracketCodes(code.replace(/"[^"]*"/g, ""), () => "");
255
264
  return /[dyhs]|m+/i.test(cleaned) && /[dyhs]/i.test(cleaned);
256
265
  }
257
266
  function tokenizeDateFormat(format) {
@@ -442,18 +451,19 @@ function formatExcelDate(rawValue, format, date1904) {
442
451
  case "elapsed-s":
443
452
  out += elapsed(totalSeconds, t.text.length);
444
453
  break;
445
- case "subsec":
446
- out += (totalSeconds - Math.floor(totalSeconds)).toFixed(subsecDigits).substring(1);
454
+ case "subsec": {
455
+ const places = Math.min(subsecDigits, 100);
456
+ const fraction = (totalSeconds - Math.floor(totalSeconds)).toFixed(places);
457
+ out += fraction.substring(1) + "0".repeat(subsecDigits - places);
447
458
  break;
459
+ }
448
460
  case "m":
449
461
  out += t.text.length >= 2 ? pad2(minute) : String(minute);
450
462
  break;
451
463
  case "s":
452
464
  out += t.text.length >= 2 ? pad2(second) : String(second);
453
465
  break;
454
- case "ampm":
455
- out += ampmLabel(t.text);
456
- break;
466
+ case "ampm": out += ampmLabel(t.text);
457
467
  }
458
468
  return out;
459
469
  }
@@ -469,23 +479,28 @@ function excelExponent(rendered) {
469
479
  return `${m[1]}E${m[2] === "-" ? "-" : "+"}${m[3].padStart(2, "0")}`;
470
480
  }
471
481
  /**
472
- * A General number rounded to the decimals that fit `maxChars` characters.
473
- *
474
- * General is not a fixed format: a spreadsheet shows as many decimal places as
475
- * the column has room for and ROUNDS to that, so 4.3900875881221957 in a
476
- * column eight characters wide reads 4.390088. We rendered every stored digit
477
- * and let the cell clip it, which turns the same value into 4.390087 — off by
478
- * one in the last place shown, with nothing to say a digit was cut.
482
+ * The most characters General writes in a cell, its sign apart: however wide
483
+ * the column, 123456789012345 is 1.23457E+14 and 0.123456789012345 is
484
+ * 0.123456789 — Excel's own PDF (2026-10-02).
485
+ */
486
+ var GENERAL_CELL_CHARS = 11;
487
+ /**
488
+ * A General number in its column: the most precise of the ways General may
489
+ * write it ({@link generalRenderings}) that `fits`.
479
490
  *
480
- * The integer part is never dropped: a number too wide even without decimals
481
- * keeps them all and the cell says so its own way (see hashOnOverflow).
491
+ * General is not a fixed format: a spreadsheet shows as many places as the
492
+ * column has room for and ROUNDS to that, so 4.3900875881221957 in a column
493
+ * eight characters wide reads 4.390088. We rendered every stored digit and let
494
+ * the cell clip it, which turns the same value into 4.390087 — off by one in
495
+ * the last place shown, with nothing to say a digit was cut. A number too wide
496
+ * for every way of writing it comes back in its finest, and the cell fills
497
+ * with `#` (CellProperties.hashOnOverflow); one under 1 always fits as `0` or
498
+ * `-0`, which is what Excel prints in a column too narrow for anything else.
482
499
  *
483
- * `fits` decides, and it must measure in the face this will be DRAWN in rather
484
- * than count characters against the column's width in the document's unit.
485
- * Counted that way we produced nine digits for a column that holds nine of
486
- * Excel's and eight of ours, and the layout clipped the ninth — the very
487
- * truncation this exists to prevent. Fewer digits than the reference shows is a
488
- * coarser number; a clipped one is a different number.
500
+ * `fits` decides whether a rendering has room. The print model asks it in the
501
+ * document's own terms — the column's width less the padding it carries, in
502
+ * the font's widest digits — because that is the room Excel gives up places
503
+ * against, whatever face the page is drawn in.
489
504
  *
490
505
  * @param rawValue The cell's stored value.
491
506
  * @param fits Whether a rendering will fit the cell.
@@ -493,36 +508,100 @@ function excelExponent(rendered) {
493
508
  function generalToWidth(rawValue, fits) {
494
509
  const n = Number(rawValue);
495
510
  if (!Number.isFinite(n)) return defaultNumberRender(rawValue);
496
- const full = defaultNumberRender(rawValue);
497
- if (fits(full)) return full;
498
- const dot = full.indexOf(".");
499
- if (dot < 0) return scientificToWidth(n, fits) ?? full;
500
- let shortened = full;
501
- for (let decimals = full.length - dot - 2; decimals >= 0; decimals--) {
502
- const shifted = Number(`${Number(n.toPrecision(15))}e${decimals}`);
503
- const rounded = shifted < 0 ? -Math.round(-shifted) : Math.round(shifted);
504
- const back = Number(`${rounded}e${-decimals}`);
505
- if (!Number.isFinite(back)) break;
506
- shortened = defaultNumberRender(String(back));
507
- if (fits(shortened)) return shortened;
511
+ let widest;
512
+ for (const text of generalRenderings(n, GENERAL_CELL_CHARS)) {
513
+ if (fits(text)) return text;
514
+ widest ??= text;
508
515
  }
509
- return scientificToWidth(n, fits) ?? shortened;
516
+ return widest ?? defaultNumberRender(rawValue);
510
517
  }
511
518
  /**
512
- * `value` in scientific notation with as many mantissa decimals as `fits`
513
- * allows, spelled Excel's way: an upper-case `E`, a signed exponent of at least
514
- * two digits. Undefined when even a bare `1E+09` will not fit — the cell then
515
- * says so with hashes rather than shrinking further.
519
+ * Every way General may write `value` in `chars` characters, the most precise
520
+ * first. One rule, measured in Excel's own PDF on chart axes, which give it
521
+ * nine characters with the sign among them, and in cells, eleven with the
522
+ * sign apart (2026-10-01/02):
523
+ *
524
+ * - the number as it is, its first 15 significant digits, where that fits:
525
+ * 0.000012345, 0.0000003, 12345678901;
526
+ * - else rounded to fewer places or written in scientific notation, whichever
527
+ * keeps the finer last digit — 0.123456789, not 1.23457E-01; 1.23457E-10,
528
+ * not 0; 4E-04, not 0, in a column five characters wide — and of two as
529
+ * fine, the plain one down to 0.0001 and the scientific one below it: 0.0001
530
+ * for 0.000123456 but 1E-05 for 1.00049E-05;
531
+ * - a whole part longer than the room only in scientific notation, and never
532
+ * with the zeros a mantissa or a fraction ends in: 1E+10, 2.5E+12.
533
+ *
534
+ * The digits are the 15 of {@link decimalOf}, rounded half away from zero, so
535
+ * 1.2345E-05 is 1.235E-05, where `toExponential` rounded the binary double
536
+ * down to 1.234E-05.
537
+ *
538
+ * @param value The number.
539
+ * @param chars How many characters General has.
540
+ * @param signCounts Whether a minus sign takes one of them.
541
+ * @returns The renderings, sign included, finest first.
516
542
  */
517
- function scientificToWidth(value, fits) {
518
- for (let decimals = MAX_SCIENTIFIC_DECIMALS; decimals >= 0; decimals--) {
519
- const [mantissa, exp] = value.toExponential(decimals).split("e");
520
- const out = `${mantissa}E${exp.startsWith("-") ? "-" : "+"}${exp.replace(/^[-+]/, "").padStart(2, "0")}`;
521
- if (fits(out)) return out;
543
+ function* generalRenderings(value, chars, signCounts = false) {
544
+ if (value === 0 || !Number.isFinite(value)) {
545
+ yield value === 0 ? "0" : String(value);
546
+ return;
547
+ }
548
+ const sign = value < 0 ? "-" : "";
549
+ const room = chars - (signCounts ? sign.length : 0);
550
+ const { digits, exponent } = decimalOf(value);
551
+ let shown = digits.length;
552
+ while (shown > 1 && digits[shown - 1] === "0") shown--;
553
+ const exactPlaces = Math.max(0, shown - 1 - exponent);
554
+ const exact = placeDigits(digits, exponent, exactPlaces);
555
+ if (exact.length <= room) yield sign + exact;
556
+ const forms = [];
557
+ if (exponent < room) {
558
+ const most = Math.min(exactPlaces, room - Math.max(exponent, 0) - 2);
559
+ for (let places = Math.max(0, most); places >= 0; places--) {
560
+ const text = withoutTrailingZeros(placeDigits(digits, exponent, places));
561
+ if (text.length <= room) forms.push({
562
+ text,
563
+ place: -decimalsIn(text),
564
+ plain: true
565
+ });
566
+ }
567
+ }
568
+ const tail = (e) => `E${e < 0 ? "-" : "+"}${String(Math.abs(e)).padStart(2, "0")}`;
569
+ for (let places = Math.max(0, room - tail(exponent).length - 2); places >= 0; places--) {
570
+ let e = exponent;
571
+ let mantissa = placeDigits(digits, 0, places);
572
+ if (mantissa.length > 1 && mantissa[1] !== ".") {
573
+ e += 1;
574
+ mantissa = placeDigits("1", 0, places);
575
+ }
576
+ const stripped = withoutTrailingZeros(mantissa);
577
+ const text = stripped + tail(e);
578
+ if (text.length <= room) forms.push({
579
+ text,
580
+ place: e - decimalsIn(stripped),
581
+ plain: false
582
+ });
583
+ }
584
+ const plainFirst = exponent >= -4;
585
+ forms.sort((a, b) => a.place - b.place || (a.plain === b.plain ? 0 : a.plain === plainFirst ? -1 : 1));
586
+ const seen = new Set(exact.length <= room ? [exact] : []);
587
+ for (const form of forms) {
588
+ if (seen.has(form.text)) continue;
589
+ seen.add(form.text);
590
+ yield sign + form.text;
522
591
  }
523
592
  }
524
- /** Excel never shows more mantissa decimals than this under General. */
525
- var MAX_SCIENTIFIC_DECIMALS = 10;
593
+ /** A decimal without the zeros that end its fraction, nor a point left bare. */
594
+ function withoutTrailingZeros(fixed) {
595
+ if (!fixed.includes(".")) return fixed;
596
+ let end = fixed.length;
597
+ while (fixed[end - 1] === "0") end--;
598
+ return fixed.slice(0, fixed[end - 1] === "." ? end - 1 : end);
599
+ }
600
+ /** How many places a plain decimal shows after its point. */
601
+ var decimalsIn = (fixed) => {
602
+ const dot = fixed.indexOf(".");
603
+ return dot < 0 ? 0 : fixed.length - dot - 1;
604
+ };
526
605
  function applyFormatString(rawValue, format) {
527
606
  const n = Number(rawValue);
528
607
  const sections = splitSections(format);
@@ -571,6 +650,43 @@ function countDigitPlaceholders(s) {
571
650
  return n;
572
651
  }
573
652
  /**
653
+ * The decimal a spreadsheet takes a double for: its first 15 significant
654
+ * digits, and the power of ten the first of them stands at.
655
+ *
656
+ * Excel shows a number as those 15 digits and zeros after them, however many
657
+ * places its format asks for: 0.1 under thirty places is 0.1 and twenty-nine
658
+ * zeros, 1/3 is fifteen threes and zeros, 1E+21 under `0.00` is a 1, twenty-one
659
+ * zeros and `.00` — its own PDF of each (2026-10-01).
660
+ */
661
+ function decimalOf(value) {
662
+ const [mantissa, exponent] = Math.abs(value).toExponential(14).split("e");
663
+ return {
664
+ digits: mantissa.replace(".", ""),
665
+ exponent: Number(exponent)
666
+ };
667
+ }
668
+ /**
669
+ * Those digits at `decimals` places — the first standing at `exponent` —
670
+ * rounded half away from zero, as plain digits with a point and no sign.
671
+ */
672
+ function placeDigits(digits, exponent, decimals) {
673
+ const kept = exponent + 1 + decimals;
674
+ let units;
675
+ if (kept >= digits.length) units = digits + "0".repeat(kept - digits.length);
676
+ else if (kept < 0) units = "";
677
+ else units = digits[kept] >= "5" ? incremented(digits.slice(0, kept)) : digits.slice(0, kept);
678
+ const padded = units.replace(/^0+/, "").padStart(decimals + 1, "0");
679
+ const whole = padded.slice(0, padded.length - decimals);
680
+ return decimals > 0 ? `${whole}.${padded.slice(whole.length)}` : whole;
681
+ }
682
+ /** A string of decimal digits plus one. */
683
+ function incremented(digits) {
684
+ let at = digits.length - 1;
685
+ while (at >= 0 && digits[at] === "9") at--;
686
+ if (at < 0) return `1${"0".repeat(digits.length)}`;
687
+ return `${digits.slice(0, at)}${String(Number(digits[at]) + 1)}${"0".repeat(digits.length - at - 1)}`;
688
+ }
689
+ /**
574
690
  * `value` rounded to `decimals` places the way a spreadsheet rounds: on the
575
691
  * DECIMAL number, half away from zero.
576
692
  *
@@ -580,21 +696,19 @@ function countDigitPlaceholders(s) {
580
696
  * every other reader show 1.0%. AverageTaxRates.xlsx had eleven of its
581
697
  * percentages a tenth low for exactly this reason.
582
698
  *
583
- * Rounding to 15 significant digits first collapses the binary noise back to
584
- * the decimal the author typed; shifting through a string exponent then keeps
585
- * the scaling exact, so the final round sees 9.5 and not 9.499999999999998.
699
+ * The digits are the 15 of {@link decimalOf} and zeros after them, placed by
700
+ * hand: past seventeen significant digits `toFixed` printed the binary
701
+ * expansion (0.1 under twenty places as 0.10000000000000000555), from 1E+21 on
702
+ * it wrote the exponent (`1e+21.00`), and past a hundred places it threw.
586
703
  */
587
704
  function toFixedDecimal(value, decimals) {
588
- if (!Number.isFinite(value)) return value.toFixed(decimals);
589
- const decimal = Number(value.toPrecision(15));
590
- const shifted = Number(`${decimal}e${decimals}`);
591
- if (!Number.isFinite(shifted)) return value.toFixed(decimals);
592
- const rounded = shifted < 0 ? -Math.round(-shifted) : Math.round(shifted);
593
- const back = Number(`${rounded}e${-decimals}`);
594
- return Number.isFinite(back) ? back.toFixed(decimals) : value.toFixed(decimals);
705
+ if (!Number.isFinite(value)) return String(value);
706
+ const { digits, exponent } = decimalOf(value);
707
+ const placed = placeDigits(digits, exponent, decimals);
708
+ return value < 0 && /[1-9]/.test(placed) ? `-${placed}` : placed;
595
709
  }
596
710
  function formatScientific(value, cleaned, negativeSection) {
597
- const m = /^(.*?)([eE])([+-])(.*)$/.exec(cleaned);
711
+ const m = hasLineEnd(cleaned) ? null : /^(.*?)([eE])([+-])(.*)$/.exec(cleaned);
598
712
  if (!m) return cleaned;
599
713
  const mantissaFmt = m[1];
600
714
  const eChar = m[2];
@@ -604,17 +718,16 @@ function formatScientific(value, cleaned, negativeSection) {
604
718
  const intDigits = Math.max(1, countDigitPlaceholders(dot >= 0 ? mantissaFmt.slice(0, dot) : mantissaFmt));
605
719
  const decimals = dot >= 0 ? countDigitPlaceholders(mantissaFmt.slice(dot + 1)) : 0;
606
720
  let exp = 0;
607
- let mant = Math.abs(value);
608
- if (mant !== 0) {
609
- exp = Math.floor(Math.log10(mant));
610
- exp -= (exp % intDigits + intDigits) % intDigits;
611
- mant = mant / Math.pow(10, exp);
612
- if (Number(mant.toFixed(decimals)) >= Math.pow(10, intDigits)) {
721
+ let mantStr = placeDigits("0", 0, decimals);
722
+ if (value !== 0) {
723
+ const { digits, exponent } = decimalOf(value);
724
+ exp = exponent - (exponent % intDigits + intDigits) % intDigits;
725
+ mantStr = placeDigits(digits, exponent - exp, decimals);
726
+ if (mantStr.split(".")[0].length > intDigits) {
613
727
  exp += intDigits;
614
- mant = mant / Math.pow(10, intDigits);
728
+ mantStr = placeDigits(digits, exponent - exp, decimals);
615
729
  }
616
730
  }
617
- const mantStr = mant.toFixed(decimals);
618
731
  const expDigits = countDigitPlaceholders(expFmt) || 2;
619
732
  const expStr = String(Math.abs(exp)).padStart(expDigits, "0");
620
733
  return `${value < 0 && !negativeSection ? "-" : ""}${mantStr}${eChar}${exp < 0 ? "-" : expSignFmt === "+" ? "+" : ""}${expStr}`;
@@ -629,11 +742,77 @@ function formatScientific(value, cleaned, negativeSection) {
629
742
  * conditions) is decoration here and goes.
630
743
  */
631
744
  function resolveBracketCodes(format) {
632
- return format.replace(/\[([^\]]*)\]/g, (_, body) => {
745
+ return replaceBracketCodes(format, (body) => {
633
746
  const symbol = /^\$([^-]*)(?:-.*)?$/.exec(body)?.[1]?.replace(/"/g, "") ?? "";
634
747
  return symbol.length > 0 ? `"${symbol}"` : "";
635
748
  });
636
749
  }
750
+ var isPlaceholder = (c) => c === "0" || c === "#" || c === "?";
751
+ var isAsciiDigit = (c) => c !== void 0 && c >= "0" && c <= "9";
752
+ var isSpace = (c) => c !== void 0 && /\s/.test(c);
753
+ var isLineEnd = (c) => c === 10 || c === 13 || c === 8232 || c === 8233;
754
+ function hasLineEnd(s) {
755
+ for (let i = 0; i < s.length; i++) if (isLineEnd(s.charCodeAt(i))) return true;
756
+ return false;
757
+ }
758
+ /**
759
+ * A fraction section cut into head, numerator, denominator and tail — what
760
+ * `/^(.*?)([0#?]+)\s*\/\s*([0#?]+|\d+)(.*)$/` makes of it, in one pass. The
761
+ * expression, lazy at the front, took every position of a run of placeholders
762
+ * in turn as the start of the numerator and read the rest of the run from
763
+ * each: quadratic in a long run no slash follows. A start inside a run sees
764
+ * the same rest as the run's first, so only the first is tried. The head
765
+ * cannot cross a line end, nor the tail hold one.
766
+ */
767
+ function fractionParts(s) {
768
+ let firstEnd = s.length;
769
+ let lastEnd = -1;
770
+ for (let i = 0; i < s.length; i++) {
771
+ if (!isLineEnd(s.charCodeAt(i))) continue;
772
+ firstEnd = Math.min(firstEnd, i);
773
+ lastEnd = i;
774
+ }
775
+ for (let start = 0; start < firstEnd;) {
776
+ if (!isPlaceholder(s[start])) {
777
+ start++;
778
+ continue;
779
+ }
780
+ let numEnd = start;
781
+ while (isPlaceholder(s[numEnd])) numEnd++;
782
+ let slash = numEnd;
783
+ while (isSpace(s[slash])) slash++;
784
+ if (s[slash] === "/") {
785
+ let den = slash + 1;
786
+ while (isSpace(s[den])) den++;
787
+ let denEnd = den;
788
+ const inDen = isPlaceholder(s[den]) ? isPlaceholder : isAsciiDigit;
789
+ while (inDen(s[denEnd])) denEnd++;
790
+ if (denEnd > den && lastEnd < denEnd) return {
791
+ head: s.slice(0, start),
792
+ num: s.slice(start, numEnd),
793
+ den: s.slice(den, denEnd),
794
+ tail: s.slice(denEnd)
795
+ };
796
+ }
797
+ start = numEnd;
798
+ }
799
+ }
800
+ /**
801
+ * The last run of placeholders in the head and what follows it — what
802
+ * `/([0#?]+)([^0#?]*)$/` matches, read back from the end: that expression
803
+ * retried every placeholder of an earlier run, and is quadratic in a long one.
804
+ */
805
+ function lastPlaceholderRun(head) {
806
+ let end = head.length;
807
+ while (end > 0 && !isPlaceholder(head[end - 1])) end--;
808
+ if (end === 0) return void 0;
809
+ let at = end - 1;
810
+ while (at > 0 && isPlaceholder(head[at - 1])) at--;
811
+ return {
812
+ at,
813
+ after: head.slice(end)
814
+ };
815
+ }
637
816
  /**
638
817
  * §18.8.31 fraction formats — `# ?/?`, `# ??/??`, `?/?`, `# ?/16`.
639
818
  *
@@ -644,15 +823,13 @@ function resolveBracketCodes(format) {
644
823
  * section is not a fraction, leaving the ordinary grammar to run.
645
824
  */
646
825
  function formatFraction(value, cleaned, negativeSection) {
647
- const m = /^(.*?)([0#?]+)\s*\/\s*([0#?]+|\d+)(.*)$/.exec(cleaned);
826
+ const m = fractionParts(cleaned);
648
827
  if (!m) return void 0;
649
- const head = m[1];
650
- const denFmt = m[3];
651
- const tail = m[4];
652
- const intMatch = /([0#?]+)([^0#?]*)$/.exec(head);
653
- const hasInteger = intMatch !== null;
654
- const literalPrefix = unquoteLiteral(head.substring(0, intMatch ? intMatch.index : head.length));
655
- const separator = intMatch ? unquoteLiteral(intMatch[2]) : " ";
828
+ const { head, den: denFmt, tail } = m;
829
+ const intMatch = lastPlaceholderRun(head);
830
+ const hasInteger = intMatch !== void 0;
831
+ const literalPrefix = unquoteLiteral(head.substring(0, intMatch ? intMatch.at : head.length));
832
+ const separator = intMatch ? unquoteLiteral(intMatch.after) : " ";
656
833
  const magnitude = Math.abs(value);
657
834
  const whole = hasInteger ? Math.floor(magnitude) : 0;
658
835
  const rest = magnitude - whole;
@@ -724,7 +901,7 @@ function applyNumericSection(value, format, negativeSection) {
724
901
  for (let i = 0; i < intPlaceholders.length - significant.length; i++) if (intPlaceholders[i] === "0") zeros += "0";
725
902
  else if (intPlaceholders[i] === "?") spaces += " ";
726
903
  const intDigits = zeros + significant;
727
- let numberPart = spaces + (useThousands ? intDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : intDigits);
904
+ let numberPart = spaces + (useThousands ? groupThousands(intDigits) : intDigits);
728
905
  if (decimals > 0) {
729
906
  let dec = decRaw ?? "".padEnd(decimals, "0");
730
907
  if (dec.length < decimals) dec = dec.padEnd(decimals, "0");
@@ -739,6 +916,34 @@ function applyNumericSection(value, format, negativeSection) {
739
916
  if (value < 0 && !negativeSection) signPrefix = "-";
740
917
  return `${literalPrefix}${signPrefix}${numberPart}${isPercent ? "%" : ""}${literalSuffix}`;
741
918
  }
919
+ /**
920
+ * The digits with a comma before every third from the right of each run — what
921
+ * `/\B(?=(\d{3})+(?!\d))/g` puts one at — in one pass. That expression read
922
+ * ahead over the rest of the run from every position, and the run is as long
923
+ * as the format has zeros, applied in every cell the format shows. A comma
924
+ * goes where the rest of the run is a whole number of threes and the character
925
+ * before is a word character, as `\B` asks: a digit inside the run, a letter
926
+ * at its start.
927
+ */
928
+ function groupThousands(s) {
929
+ const isWord = (i) => i >= 0 && /\w/.test(s[i]);
930
+ let out = "";
931
+ let from = 0;
932
+ for (let start = 0; start < s.length;) {
933
+ if (!isAsciiDigit(s[start])) {
934
+ start++;
935
+ continue;
936
+ }
937
+ let end = start;
938
+ while (isAsciiDigit(s[end])) end++;
939
+ for (let at = start; at < end; at++) if ((end - at) % 3 === 0 && isWord(at - 1)) {
940
+ out += `${s.slice(from, at)},`;
941
+ from = at;
942
+ }
943
+ start = end;
944
+ }
945
+ return out + s.slice(from);
946
+ }
742
947
  function splitNumberFormat(cleaned) {
743
948
  let firstDigit = -1;
744
949
  let lastDigit = -1;
@@ -802,4 +1007,4 @@ function unquoteLiteral(s) {
802
1007
  return out;
803
1008
  }
804
1009
  //#endregion
805
- export { applyNumberFormat, excelSerialFromUtcParts, excelSerialToDate, generalToWidth, numberFormatColorHex };
1010
+ export { applyNumberFormat, excelSerialFromUtcParts, excelSerialToDate, generalRenderings, generalToWidth, numberFormatColorHex };
@@ -1,2 +1,3 @@
1
- import "./state.js";
2
- import "./apply.js";
1
+ import { formatCounter } from "./state.js";
2
+ import { applyNumbering, applyNumberingToHeadersFooters } from "./apply.js";
3
+ export { applyNumbering, applyNumberingToHeadersFooters, formatCounter };
@@ -13,7 +13,7 @@ var ENDOFCHAIN = 4294967294;
13
13
  var FREESECT = 4294967295;
14
14
  var HEADER_SIZE = 512;
15
15
  var DIR_ENTRY_SIZE = 128;
16
- var MAX_TOTAL_BYTES = 512 * 1024 * 1024;
16
+ var MAX_TOTAL_BYTES = 536870912;
17
17
  /** Error thrown for a malformed or unsupported compound file. */
18
18
  var CfbError = class extends Error {};
19
19
  /**
@@ -134,7 +134,7 @@ function openCfb(bytes) {
134
134
  const ensureMini = () => {
135
135
  if (miniStream && miniFat) return;
136
136
  miniStream = readChainBytes(root.startSector, MAX_TOTAL_BYTES).subarray(0, root.size);
137
- const mfBytes = firstMiniFatSector === ENDOFCHAIN ? new Uint8Array(0) : readChainBytes(firstMiniFatSector, MAX_TOTAL_BYTES);
137
+ const mfBytes = firstMiniFatSector === ENDOFCHAIN ? /* @__PURE__ */ new Uint8Array(0) : readChainBytes(firstMiniFatSector, MAX_TOTAL_BYTES);
138
138
  const mfView = new DataView(mfBytes.buffer, mfBytes.byteOffset, mfBytes.byteLength);
139
139
  miniFat = new Uint32Array(Math.floor(mfBytes.length / 4));
140
140
  for (let i = 0; i < miniFat.length; i++) miniFat[i] = mfView.getUint32(i * 4, true);
@@ -159,7 +159,7 @@ function openCfb(bytes) {
159
159
  return out;
160
160
  };
161
161
  const readEntry = (entry) => {
162
- if (entry.size === 0) return new Uint8Array(0);
162
+ if (entry.size === 0) return /* @__PURE__ */ new Uint8Array(0);
163
163
  if (entry.size > MAX_TOTAL_BYTES) throw new CfbError("stream exceeds size limit");
164
164
  if (entry.size >= miniCutoff) return readChainBytes(entry.startSector, MAX_TOTAL_BYTES).subarray(0, entry.size);
165
165
  return readMiniStream(entry);
@@ -201,4 +201,4 @@ function decodeName(dir, base, nameLen) {
201
201
  return out;
202
202
  }
203
203
  //#endregion
204
- export { isCfb, openCfb };
204
+ export { CfbError, isCfb, openCfb };
@@ -11,7 +11,7 @@ var BLIP_TIFF = 61481;
11
11
  var BLIP_JPEG_CMYK = 61482;
12
12
  var METAFILE_HEADER_BYTES = 34;
13
13
  var COMPRESSION_DEFLATE = 0;
14
- var TWO_UID_INSTANCES = new Set([
14
+ var TWO_UID_INSTANCES = /* @__PURE__ */ new Set([
15
15
  981,
16
16
  535,
17
17
  1347,
@@ -1,4 +1,5 @@
1
- import "./relationship-types.js";
2
- import "./package.js";
3
- import "./core-properties.js";
4
- import "./opc-writer.js";
1
+ import { isOoxmlRel } from "./relationship-types.js";
2
+ import { OpcPackage } from "./package.js";
3
+ import { parseCoreProperties } from "./core-properties.js";
4
+ import { buildOpcPackage } from "./opc-writer.js";
5
+ export { OpcPackage, buildOpcPackage, isOoxmlRel, parseCoreProperties };
@@ -3,13 +3,15 @@ import { isOoxmlRel } from "./relationship-types.js";
3
3
  import { unzipSync } from "fflate";
4
4
  //#region src/core/opc/package.ts
5
5
  var ROOT_RELS_PATH = "_rels/.rels";
6
- var MIB = 1024 * 1024;
6
+ var MIB = 1048576;
7
7
  /**
8
8
  * An opened OPC package (ECMA-376 Part 2): a ZIP archive's parts plus its
9
9
  * package-level relationships, with helpers to look up parts, resolve part
10
10
  * relationships, and find the main document part.
11
11
  */
12
12
  var OpcPackage = class OpcPackage {
13
+ parts;
14
+ rootRelationships;
13
15
  /**
14
16
  * @param parts The package parts keyed by ZIP-convention path (no leading slash).
15
17
  * @param rootRelationships The package-level relationships from `_rels/.rels`.
@@ -0,0 +1,15 @@
1
+ /** Whether the code unit is one `\w` matches. NaN, past the end, is not. */
2
+ export declare function isWordChar(c: number): boolean;
3
+ /**
4
+ * The matches a global search with `re` finds in `xml` from `from`, in order.
5
+ *
6
+ * @param xml The part's text.
7
+ * @param open The tag's opening, `<w:font`, which `re` begins with.
8
+ * @param boundary Whether `re` puts `\b` right after `open`.
9
+ * @param re The expression, sticky (`y`): `open`, the `\b` if any, then
10
+ * `[^>]*` (greedy or lazy) before what it looks for in the tag.
11
+ * @param from Where the search starts.
12
+ */
13
+ export declare function tagMatches(xml: string, open: string, boundary: boolean, re: RegExp, from?: number): Generator<RegExpExecArray>;
14
+ /** The first of {@link tagMatches}, or undefined when there is none. */
15
+ export declare function firstTagMatch(xml: string, open: string, boundary: boolean, re: RegExp, from?: number): RegExpExecArray | undefined;
@@ -0,0 +1,41 @@
1
+ //#region src/core/opc/tag-scan.ts
2
+ /** Whether the code unit is one `\w` matches. NaN, past the end, is not. */
3
+ function isWordChar(c) {
4
+ return c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
5
+ }
6
+ /**
7
+ * The matches a global search with `re` finds in `xml` from `from`, in order.
8
+ *
9
+ * @param xml The part's text.
10
+ * @param open The tag's opening, `<w:font`, which `re` begins with.
11
+ * @param boundary Whether `re` puts `\b` right after `open`.
12
+ * @param re The expression, sticky (`y`): `open`, the `\b` if any, then
13
+ * `[^>]*` (greedy or lazy) before what it looks for in the tag.
14
+ * @param from Where the search starts.
15
+ */
16
+ function* tagMatches(xml, open, boundary, re, from = 0) {
17
+ let at = xml.indexOf(open, from);
18
+ while (at >= 0) {
19
+ if (boundary && isWordChar(xml.charCodeAt(at + open.length))) {
20
+ at = xml.indexOf(open, at + 1);
21
+ continue;
22
+ }
23
+ re.lastIndex = at;
24
+ const found = re.exec(xml);
25
+ if (found) {
26
+ const next = Math.max(re.lastIndex, at + 1);
27
+ yield found;
28
+ at = xml.indexOf(open, next);
29
+ continue;
30
+ }
31
+ const end = xml.indexOf(">", at + open.length);
32
+ if (end < 0) return;
33
+ at = xml.indexOf(open, end + 1);
34
+ }
35
+ }
36
+ /** The first of {@link tagMatches}, or undefined when there is none. */
37
+ function firstTagMatch(xml, open, boundary, re, from = 0) {
38
+ for (const found of tagMatches(xml, open, boundary, re, from)) return found;
39
+ }
40
+ //#endregion
41
+ export { firstTagMatch, isWordChar, tagMatches };