ooxml-core 0.21.2 → 0.22.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 (78) hide show
  1. package/README.md +3 -4
  2. package/dist/automation/index.cjs +34 -13
  3. package/dist/automation/index.mjs +34 -13
  4. package/dist/chart/index.cjs +242 -0
  5. package/dist/chart/index.d.ts +5 -0
  6. package/dist/chart/index.d.ts.map +1 -1
  7. package/dist/chart/index.mjs +227 -1
  8. package/dist/chart/pie-best-fit.d.ts +51 -0
  9. package/dist/chart/pie-best-fit.d.ts.map +1 -0
  10. package/dist/chart/pie-label-collision.d.ts +48 -0
  11. package/dist/chart/pie-label-collision.d.ts.map +1 -0
  12. package/dist/chart/radar-geometry.d.ts +19 -0
  13. package/dist/chart/radar-geometry.d.ts.map +1 -0
  14. package/dist/chart/treemap-squarify.d.ts +31 -0
  15. package/dist/chart/treemap-squarify.d.ts.map +1 -0
  16. package/dist/chart/trendline-equation.d.ts +39 -0
  17. package/dist/chart/trendline-equation.d.ts.map +1 -0
  18. package/dist/collab/index.cjs +6 -1
  19. package/dist/collab/index.mjs +6 -1
  20. package/dist/color/contrast.d.ts +18 -0
  21. package/dist/color/contrast.d.ts.map +1 -0
  22. package/dist/color/index.cjs +33 -0
  23. package/dist/color/index.d.ts +2 -0
  24. package/dist/color/index.d.ts.map +1 -1
  25. package/dist/color/index.mjs +31 -1
  26. package/dist/color/unit-rgb.d.ts +21 -0
  27. package/dist/color/unit-rgb.d.ts.map +1 -0
  28. package/dist/docx/embedded.cjs +1 -1
  29. package/dist/docx/embedded.mjs +1 -1
  30. package/dist/docx/index.cjs +1 -1
  31. package/dist/docx/index.mjs +1 -1
  32. package/dist/docx/load/index.cjs +1 -1
  33. package/dist/docx/load/index.mjs +1 -1
  34. package/dist/geometry/align-distribute.d.ts +145 -0
  35. package/dist/geometry/align-distribute.d.ts.map +1 -0
  36. package/dist/geometry/index.cjs +318 -0
  37. package/dist/geometry/index.d.ts +2 -0
  38. package/dist/geometry/index.d.ts.map +1 -1
  39. package/dist/geometry/index.mjs +307 -1
  40. package/dist/geometry/snap-guides.d.ts +125 -0
  41. package/dist/geometry/snap-guides.d.ts.map +1 -0
  42. package/dist/index.cjs +665 -0
  43. package/dist/index.mjs +665 -0
  44. package/dist/pptx/automation/index.cjs +24 -1
  45. package/dist/pptx/automation/index.d.ts +3 -3
  46. package/dist/pptx/automation/index.mjs +24 -1
  47. package/dist/pptx/chunks/{index-JaNVJ6Tf.d.ts → index-D2y7ehZ5.d.ts} +3 -3
  48. package/dist/pptx/chunks/{index-JaNVJ6Tf.d.ts.map → index-D2y7ehZ5.d.ts.map} +1 -1
  49. package/dist/pptx/chunks/{index-DHdHU9Qr.d.ts → index-ucqKcqvI.d.ts} +10 -2
  50. package/dist/pptx/chunks/{index-DHdHU9Qr.d.ts.map → index-ucqKcqvI.d.ts.map} +1 -1
  51. package/dist/pptx/chunks/{text-operations-DUEhSW5Z.d.ts → text-operations-CR3pUJi-.d.ts} +14 -2
  52. package/dist/pptx/chunks/text-operations-CR3pUJi-.d.ts.map +1 -0
  53. package/dist/pptx/cli/index.cjs +23 -0
  54. package/dist/pptx/cli/index.d.ts +1 -1
  55. package/dist/pptx/cli/index.mjs +23 -0
  56. package/dist/pptx/converter/index.d.ts +1 -1
  57. package/dist/pptx/index.cjs +33 -3
  58. package/dist/pptx/index.d.ts +3 -3
  59. package/dist/pptx/index.mjs +33 -3
  60. package/dist/teams/index.cjs +7 -2
  61. package/dist/teams/index.mjs +7 -2
  62. package/dist/text/decimal-tab.d.ts +24 -0
  63. package/dist/text/decimal-tab.d.ts.map +1 -0
  64. package/dist/text/index.cjs +72 -0
  65. package/dist/text/index.d.ts +2 -0
  66. package/dist/text/index.d.ts.map +1 -1
  67. package/dist/text/index.mjs +68 -1
  68. package/dist/text/tab-leader.d.ts +36 -0
  69. package/dist/text/tab-leader.d.ts.map +1 -0
  70. package/dist/xlsx/edit/charts.d.ts.map +1 -1
  71. package/dist/xlsx/edit/clipboard-html-parse.d.ts.map +1 -1
  72. package/dist/xlsx/index.cjs +33 -12
  73. package/dist/xlsx/index.mjs +33 -12
  74. package/dist/xlsx/ui/grid/cell-paint.d.ts.map +1 -1
  75. package/dist/xlsx/ui/index.cjs +37 -16
  76. package/dist/xlsx/ui/index.mjs +37 -16
  77. package/package.json +2 -2
  78. package/dist/pptx/chunks/text-operations-DUEhSW5Z.d.ts.map +0 -1
package/dist/index.cjs CHANGED
@@ -14,13 +14,16 @@ __export(color_exports, {
14
14
  blendColorOntoWhite: () => blendColorOntoWhite,
15
15
  clampUnitInterval: () => clampUnitInterval,
16
16
  colorWithOpacity: () => colorWithOpacity,
17
+ contrastTextColor: () => contrastTextColor,
17
18
  hexToRgbChannels: () => hexToRgbChannels,
19
+ hexToRgbUnit: () => hexToRgbUnit,
18
20
  hslToRgb: () => hslToRgb,
19
21
  linearToSrgb255: () => linearToSrgb255,
20
22
  normalizeHexColor: () => normalizeHexColor,
21
23
  parseDrawingFraction: () => parseDrawingFraction,
22
24
  parseDrawingHueDegrees: () => parseDrawingHueDegrees,
23
25
  parseDrawingPercent: () => parseDrawingPercent,
26
+ parseHex: () => parseHex,
24
27
  parseOoxmlPercent: () => parseOoxmlPercent,
25
28
  rgbToHsl: () => rgbToHsl,
26
29
  scrgbLinearToSrgb8: () => scrgbLinearToSrgb8,
@@ -188,22 +191,68 @@ function linearToSrgb255(linear) {
188
191
  return 255 * (c <= 31308e-7 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055);
189
192
  }
190
193
 
194
+ // color/contrast.ts
195
+ function parseHex(hex) {
196
+ let h = hex.trim().replace(/^#/u, "");
197
+ if (h.length === 3) {
198
+ h = h.split("").map((c) => c + c).join("");
199
+ }
200
+ if (h.length !== 6 || /[^0-9a-fA-F]/u.test(h)) {
201
+ return [128, 128, 128];
202
+ }
203
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
204
+ }
205
+ function contrastTextColor(fill2) {
206
+ const [r, g, b] = parseHex(fill2);
207
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
208
+ return lum > 0.6 ? "#1a1a1a" : "#ffffff";
209
+ }
210
+
211
+ // color/unit-rgb.ts
212
+ function hexToRgbUnit(hex) {
213
+ const clean = hex.replace(/^#/u, "");
214
+ const r = Number.parseInt(clean.substring(0, 2), 16) / 255;
215
+ const g = Number.parseInt(clean.substring(2, 4), 16) / 255;
216
+ const b = Number.parseInt(clean.substring(4, 6), 16) / 255;
217
+ return {
218
+ r: Number.isFinite(r) ? r : 0,
219
+ g: Number.isFinite(g) ? g : 0,
220
+ b: Number.isFinite(b) ? b : 0
221
+ };
222
+ }
223
+
191
224
  // chart/index.ts
192
225
  var chart_exports = {};
193
226
  __export(chart_exports, {
227
+ BEST_FIT_EDGE_TOLERANCE: () => BEST_FIT_EDGE_TOLERANCE,
228
+ BEST_FIT_OUTSIDE_GAP: () => BEST_FIT_OUTSIDE_GAP,
229
+ BEST_FIT_RIM_INSET: () => BEST_FIT_RIM_INSET,
230
+ LABEL_COLLISION_GAP: () => LABEL_COLLISION_GAP,
194
231
  aggregateByCategory: () => aggregateByCategory,
195
232
  axisTargetIntervals: () => axisTargetIntervals,
196
233
  computeBoxStats: () => computeBoxStats,
197
234
  computeHistogramBins: () => computeHistogramBins,
198
235
  computeLinearRegression: () => computeLinearRegression,
199
236
  computeRSquared: () => computeRSquared,
237
+ computeRadarPoints: () => computeRadarPoints,
200
238
  computeStackedSeriesPlots: () => computeStackedSeriesPlots,
201
239
  fitPolynomial: () => fitPolynomial,
240
+ formatExponentialEquation: () => formatExponentialEquation,
241
+ formatLinearEquation: () => formatLinearEquation,
242
+ formatLogarithmicEquation: () => formatLogarithmicEquation,
243
+ formatPolynomialEquation: () => formatPolynomialEquation,
244
+ formatPowerEquation: () => formatPowerEquation,
245
+ formatTrendlineCoefficient: () => formatTrendlineCoefficient,
202
246
  groupRowsByCategory: () => groupRowsByCategory,
203
247
  niceAxisStep: () => niceAxisStep,
204
248
  niceValueAxisBounds: () => niceValueAxisBounds,
249
+ nudgeOutsideLabels: () => nudgeOutsideLabels,
250
+ placeBestFitLabel: () => placeBestFitLabel,
251
+ radarAngle: () => radarAngle,
252
+ radarRingPoints: () => radarRingPoints,
205
253
  resolveBlankDisplay: () => resolveBlankDisplay,
206
254
  scottBinCount: () => scottBinCount,
255
+ squarify: () => squarify,
207
256
  visibleRuns: () => visibleRuns
208
257
  });
209
258
 
@@ -587,10 +636,241 @@ function aggregateByCategory(values, rawCategories) {
587
636
  });
588
637
  }
589
638
 
639
+ // chart/treemap-squarify.ts
640
+ function worstAspectRatio(areas, side) {
641
+ const sum = areas.reduce((total, area) => total + area, 0);
642
+ const max = Math.max(...areas);
643
+ const min = Math.min(...areas);
644
+ const sideSq = side * side;
645
+ const sumSq = sum * sum;
646
+ return Math.max(sideSq * max / sumSq, sumSq / (sideSq * min));
647
+ }
648
+ function squarify(nodes, box) {
649
+ const sorted = [...nodes].sort((a, b) => b.weight - a.weight);
650
+ const total = sorted.reduce((sum, node) => sum + node.weight, 0);
651
+ const scale = total > 0 ? box.w * box.h / total : box.w * box.h / sorted.length;
652
+ const items = sorted.map((node) => ({ node, area: (total > 0 ? node.weight : 1) * scale }));
653
+ const result = [];
654
+ let rect2 = { ...box };
655
+ let remaining = items;
656
+ for (let first2 = remaining[0]; first2 !== void 0; first2 = remaining[0]) {
657
+ const side = Math.min(rect2.w, rect2.h);
658
+ let row = [first2];
659
+ let rowAreas = [first2.area];
660
+ let worst = worstAspectRatio(rowAreas, side);
661
+ let cursor = 1;
662
+ for (let next = remaining[cursor]; next !== void 0; next = remaining[cursor]) {
663
+ const candidateAreas = [...rowAreas, next.area];
664
+ const candidateWorst = worstAspectRatio(candidateAreas, side);
665
+ if (candidateWorst > worst) {
666
+ break;
667
+ }
668
+ row = [...row, next];
669
+ rowAreas = candidateAreas;
670
+ worst = candidateWorst;
671
+ cursor++;
672
+ }
673
+ const rowTotalArea = rowAreas.reduce((sum, area) => sum + area, 0);
674
+ const thickness = side > 0 ? rowTotalArea / side : 0;
675
+ const layoutVertically = rect2.w >= rect2.h;
676
+ let offset = layoutVertically ? rect2.y : rect2.x;
677
+ for (const item of row) {
678
+ const length = thickness > 0 ? item.area / thickness : 0;
679
+ if (layoutVertically) {
680
+ result.push([item.node, { x: rect2.x, y: offset, w: thickness, h: length }]);
681
+ } else {
682
+ result.push([item.node, { x: offset, y: rect2.y, w: length, h: thickness }]);
683
+ }
684
+ offset += length;
685
+ }
686
+ rect2 = layoutVertically ? { ...rect2, x: rect2.x + thickness, w: rect2.w - thickness } : { ...rect2, y: rect2.y + thickness, h: rect2.h - thickness };
687
+ remaining = remaining.slice(row.length);
688
+ }
689
+ return result;
690
+ }
691
+
692
+ // chart/trendline-equation.ts
693
+ var SIGNIFICANT_DIGITS = 4;
694
+ function formatTrendlineCoefficient(value) {
695
+ if (!Number.isFinite(value) || value === 0) {
696
+ return "0";
697
+ }
698
+ const rounded = Number(value.toPrecision(SIGNIFICANT_DIGITS));
699
+ return rounded === 0 ? "0" : String(rounded);
700
+ }
701
+ function signedTerm(value) {
702
+ return { sign: value < 0 ? "-" : "+", abs: formatTrendlineCoefficient(Math.abs(value)) };
703
+ }
704
+ function formatLinearEquation(slope, intercept) {
705
+ const { sign, abs } = signedTerm(intercept);
706
+ return `y = ${formatTrendlineCoefficient(slope)}x ${sign} ${abs}`;
707
+ }
708
+ function formatExponentialEquation(a, b) {
709
+ return `y = ${formatTrendlineCoefficient(a)} e^(${formatTrendlineCoefficient(b)}x)`;
710
+ }
711
+ function formatLogarithmicEquation(slope, intercept) {
712
+ const { sign, abs } = signedTerm(intercept);
713
+ return `y = ${formatTrendlineCoefficient(slope)}ln(x) ${sign} ${abs}`;
714
+ }
715
+ function formatPowerEquation(a, b) {
716
+ return `y = ${formatTrendlineCoefficient(a)}x^${formatTrendlineCoefficient(b)}`;
717
+ }
718
+ function formatPolynomialEquation(coeffsAscending) {
719
+ const order = coeffsAscending.length - 1;
720
+ let out = "y = ";
721
+ for (let power = order; power >= 0; power--) {
722
+ const coeff = coeffsAscending[power] ?? 0;
723
+ const { sign, abs } = signedTerm(coeff);
724
+ const variable = power === 0 ? "" : power === 1 ? "x" : `x^${power}`;
725
+ out += power === order ? `${sign === "-" ? "-" : ""}${abs}${variable}` : ` ${sign} ${abs}${variable}`;
726
+ }
727
+ return out;
728
+ }
729
+
730
+ // chart/radar-geometry.ts
731
+ function radarAngle(index, catCount) {
732
+ const n = Math.max(catCount, 1);
733
+ return Math.PI * 2 * index / n - Math.PI / 2;
734
+ }
735
+ function computeRadarPoints(values, maxVal, radius, cx, cy, catCount) {
736
+ const denom = maxVal > 0 ? maxVal : 1;
737
+ return values.slice(0, Math.max(catCount, 1)).map((val2, i) => {
738
+ const angle = radarAngle(i, catCount), r = Math.abs(val2) / denom * radius;
739
+ return { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) };
740
+ });
741
+ }
742
+ function radarRingPoints(cx, cy, rr, catCount) {
743
+ const n = Math.max(catCount, 1);
744
+ return Array.from({ length: n }, (_, i) => {
745
+ const angle = radarAngle(i, n);
746
+ return `${(cx + rr * Math.cos(angle)).toFixed(2)},${(cy + rr * Math.sin(angle)).toFixed(2)}`;
747
+ }).join(" ");
748
+ }
749
+
750
+ // chart/pie-best-fit.ts
751
+ var PX_PER_PT = 4 / 3;
752
+ var BEST_FIT_RIM_INSET = 4 * PX_PER_PT;
753
+ var BEST_FIT_EDGE_TOLERANCE = 1.5 * PX_PER_PT;
754
+ var BEST_FIT_OUTSIDE_GAP = 2 * PX_PER_PT;
755
+ function farCorner(x, y, w, h) {
756
+ return Math.hypot(Math.abs(x) + w / 2, Math.abs(y) + h / 2);
757
+ }
758
+ function insideDistance(ux, uy, w, h, limit) {
759
+ if (farCorner(0, 0, w, h) > limit) {
760
+ return void 0;
761
+ }
762
+ let lo = 0;
763
+ let hi = limit;
764
+ for (let i = 0; i < 40; i++) {
765
+ const mid = (lo + hi) / 2;
766
+ if (farCorner(mid * ux, mid * uy, w, h) > limit) {
767
+ hi = mid;
768
+ } else {
769
+ lo = mid;
770
+ }
771
+ }
772
+ return lo;
773
+ }
774
+ function edgeOverhang(x, y, w, h, slice) {
775
+ let worst = Number.NEGATIVE_INFINITY;
776
+ const corners = [
777
+ [-w / 2, -h / 2],
778
+ [w / 2, -h / 2],
779
+ [-w / 2, h / 2],
780
+ [w / 2, h / 2]
781
+ ];
782
+ for (const [dx, dy] of corners) {
783
+ const px2 = x + dx;
784
+ const py = y + dy;
785
+ const fromStart = Math.cos(slice.startAngle) * py - Math.sin(slice.startAngle) * px2;
786
+ const fromEnd = Math.sin(slice.endAngle) * px2 - Math.cos(slice.endAngle) * py;
787
+ worst = Math.max(worst, -fromStart, -fromEnd);
788
+ }
789
+ return worst;
790
+ }
791
+ function placeBestFitLabel(slice, w, h) {
792
+ const mid = (slice.startAngle + slice.endAngle) / 2;
793
+ const ux = Math.cos(mid);
794
+ const uy = Math.sin(mid);
795
+ const distance = insideDistance(ux, uy, w, h, slice.outerR - BEST_FIT_RIM_INSET);
796
+ if (distance !== void 0) {
797
+ const x = distance * ux;
798
+ const y = distance * uy;
799
+ const reflex = slice.endAngle - slice.startAngle >= Math.PI;
800
+ if (reflex || edgeOverhang(x, y, w, h, slice) <= BEST_FIT_EDGE_TOLERANCE) {
801
+ return { x: slice.cx + x, y: slice.cy + y, inside: true };
802
+ }
803
+ }
804
+ const extent = Math.abs(ux) * w / 2 + Math.abs(uy) * h / 2;
805
+ const outside = slice.outerR + BEST_FIT_OUTSIDE_GAP + extent;
806
+ return { x: slice.cx + outside * ux, y: slice.cy + outside * uy, inside: false };
807
+ }
808
+
809
+ // chart/pie-label-collision.ts
810
+ var LABEL_COLLISION_GAP = 2;
811
+ function boxDistanceToCentre(box, y, disc) {
812
+ const nearestX = Math.max(box.x - box.w / 2, Math.min(disc.cx, box.x + box.w / 2));
813
+ const nearestY = Math.max(y - box.h / 2, Math.min(disc.cy, y + box.h / 2));
814
+ return Math.hypot(nearestX - disc.cx, nearestY - disc.cy);
815
+ }
816
+ function clearOfDisc(box, y, disc) {
817
+ if (boxDistanceToCentre(box, y, disc) >= disc.r) {
818
+ return 0;
819
+ }
820
+ const sign = box.x >= disc.cx ? 1 : -1;
821
+ let dx = 0;
822
+ while (dx < disc.r * 2 && boxDistanceToCentre({ ...box, x: box.x + sign * dx }, y, disc) < disc.r) {
823
+ dx += 1;
824
+ }
825
+ return sign * dx;
826
+ }
827
+ function overlapsX(a, b) {
828
+ return Math.abs(a.x - b.x) < (a.w + b.w) / 2;
829
+ }
830
+ function sweepSide(boxes, minY, maxY) {
831
+ const sorted = [...boxes].sort((a, b) => a.y - b.y || a.id - b.id);
832
+ const ys = sorted.map((box) => box.y);
833
+ for (let i = 1; i < sorted.length; i++) {
834
+ const prev = sorted[i - 1];
835
+ const box = sorted[i];
836
+ if (overlapsX(prev, box)) {
837
+ ys[i] = Math.max(ys[i], ys[i - 1] + (prev.h + box.h) / 2 + LABEL_COLLISION_GAP);
838
+ }
839
+ }
840
+ for (let i = sorted.length - 1; i >= 0; i--) {
841
+ const box = sorted[i];
842
+ const limit = i === sorted.length - 1 ? maxY - box.h / 2 : ys[i + 1] - (box.h + sorted[i + 1].h) / 2 - LABEL_COLLISION_GAP;
843
+ if (ys[i] > limit) {
844
+ ys[i] = Math.max(limit, minY + box.h / 2);
845
+ }
846
+ }
847
+ return new Map(sorted.map((box, i) => [box.id, ys[i]]));
848
+ }
849
+ function nudgeOutsideLabels(boxes, disc, minY, maxY) {
850
+ const resolved = /* @__PURE__ */ new Map();
851
+ for (const side of [
852
+ boxes.filter((box) => box.x >= disc.cx),
853
+ boxes.filter((box) => box.x < disc.cx)
854
+ ]) {
855
+ for (const [id, y] of sweepSide(side, minY, maxY)) {
856
+ resolved.set(id, y);
857
+ }
858
+ }
859
+ return boxes.map((box) => {
860
+ const y = resolved.get(box.id) ?? box.y;
861
+ return { id: box.id, dx: y === box.y ? 0 : clearOfDisc(box, y, disc), dy: y - box.y };
862
+ });
863
+ }
864
+
590
865
  // text/index.ts
591
866
  var text_exports = {};
592
867
  __export(text_exports, {
868
+ buildLeaderString: () => buildLeaderString,
869
+ decimalAnchorIndex: () => decimalAnchorIndex,
870
+ decimalSeparatorForLanguage: () => decimalSeparatorForLanguage,
593
871
  detectFontScript: () => detectFontScript,
872
+ getLeaderCharacter: () => getLeaderCharacter,
873
+ getLeaderStyles: () => getLeaderStyles,
594
874
  hasDistinctScriptFonts: () => hasDistinctScriptFonts,
595
875
  resolveFontForScript: () => resolveFontForScript,
596
876
  segmentByScript: () => segmentByScript
@@ -712,6 +992,73 @@ function hasDistinctScriptFonts(fonts) {
712
992
  return Boolean(fonts.eastAsia) && fonts.eastAsia !== base || Boolean(fonts.complexScript) && fonts.complexScript !== base || Boolean(fonts.symbol) && fonts.symbol !== base;
713
993
  }
714
994
 
995
+ // text/tab-leader.ts
996
+ function getLeaderCharacter(leader) {
997
+ switch (leader) {
998
+ case "dot":
999
+ return ".";
1000
+ case "hyphen":
1001
+ return "-";
1002
+ case "underscore":
1003
+ return "_";
1004
+ case "heavy":
1005
+ return "\u2501";
1006
+ // box-drawing heavy horizontal
1007
+ case "middleDot":
1008
+ return "\xB7";
1009
+ // middle dot
1010
+ default:
1011
+ return void 0;
1012
+ }
1013
+ }
1014
+ function getLeaderStyles(widthPx, color) {
1015
+ if (widthPx <= 0) {
1016
+ return void 0;
1017
+ }
1018
+ return {
1019
+ display: "inline-block",
1020
+ width: `${widthPx}px`,
1021
+ overflow: "hidden",
1022
+ whiteSpace: "pre",
1023
+ verticalAlign: "baseline",
1024
+ ...color ? { color } : {}
1025
+ };
1026
+ }
1027
+ function buildLeaderString(leader, widthPx, fontSizePx = 16) {
1028
+ const char = getLeaderCharacter(leader);
1029
+ if (!char || widthPx <= 0) {
1030
+ return " ";
1031
+ }
1032
+ const charWidth = Math.max(1, fontSizePx * 0.6);
1033
+ const count = Math.max(1, Math.ceil(widthPx / charWidth));
1034
+ return char.repeat(count);
1035
+ }
1036
+
1037
+ // text/decimal-tab.ts
1038
+ function decimalSeparatorForLanguage(language) {
1039
+ if (!language) {
1040
+ return ".";
1041
+ }
1042
+ try {
1043
+ const part = new Intl.NumberFormat(language).formatToParts(1.5).find((p) => p.type === "decimal");
1044
+ return part?.value ?? ".";
1045
+ } catch {
1046
+ return ".";
1047
+ }
1048
+ }
1049
+ function decimalAnchorIndex(text, separator) {
1050
+ const first2 = text.search(/[0-9]/u);
1051
+ if (first2 < 0) {
1052
+ return text.length;
1053
+ }
1054
+ let end = first2;
1055
+ while (end < text.length && /[0-9.,]/u.test(text.charAt(end))) {
1056
+ end++;
1057
+ }
1058
+ const sep = text.slice(first2, end).indexOf(separator);
1059
+ return sep >= 0 ? first2 + sep : end;
1060
+ }
1061
+
715
1062
  // diagram/index.ts
716
1063
  var diagram_exports = {};
717
1064
  __export(diagram_exports, {
@@ -3444,6 +3791,7 @@ __export(geometry_exports, {
3444
3791
  RIBBON_CONNECTION_SITES: () => RIBBON_CONNECTION_SITES,
3445
3792
  RIBBON_TEXT_RECTS: () => RIBBON_TEXT_RECTS,
3446
3793
  SCROLLS_ACCENT_CALLOUTS_PRESET_DEFINITIONS: () => SCROLLS_ACCENT_CALLOUTS_PRESET_DEFINITIONS,
3794
+ SNAP_THRESHOLD: () => SNAP_THRESHOLD,
3447
3795
  STAR_CONNECTION_SITES: () => STAR_CONNECTION_SITES,
3448
3796
  STAR_TEXT_RECTS: () => STAR_TEXT_RECTS,
3449
3797
  ST_SHAPE_TYPE_VALUES: () => ST_SHAPE_TYPE_VALUES,
@@ -3451,17 +3799,23 @@ __export(geometry_exports, {
3451
3799
  TAB_CONNECTION_SITES: () => TAB_CONNECTION_SITES,
3452
3800
  TAB_TEXT_RECTS: () => TAB_TEXT_RECTS,
3453
3801
  _internalForTests: () => _internalForTests,
3802
+ alignElements: () => alignElements,
3454
3803
  angleToRadians: () => angleToRadians,
3455
3804
  at: () => at,
3456
3805
  buildCalloutLeaderLineSvgPath: () => buildCalloutLeaderLineSvgPath,
3457
3806
  clipPolygons: () => clipPolygons,
3458
3807
  combineShapes: () => combineShapes,
3808
+ computeAlign: () => computeAlign,
3809
+ computeDistribute: () => computeDistribute,
3810
+ computeSnap: () => computeSnap,
3811
+ computeSnapToShape: () => computeSnapToShape,
3459
3812
  computeUnionWalk: () => computeUnionWalk,
3460
3813
  convexHull: () => convexHull,
3461
3814
  createBuiltinVariables: () => createBuiltinVariables,
3462
3815
  cross2: () => cross2,
3463
3816
  cxn: () => cxn,
3464
3817
  dedupPoly: () => dedupPoly,
3818
+ distributeElements: () => distributeElements,
3465
3819
  elbowCurveSegments: () => elbowCurveSegments,
3466
3820
  elbowWaypoints: () => elbowWaypoints,
3467
3821
  ensureCCW: () => ensureCCW,
@@ -3505,10 +3859,14 @@ __export(geometry_exports, {
3505
3859
  pointInTriangle: () => pointInTriangle,
3506
3860
  polygonArea: () => polygonArea,
3507
3861
  polygonsToSvgPath: () => polygonsToSvgPath,
3862
+ resolveAlignReferenceBox: () => resolveAlignReferenceBox,
3508
3863
  resolveCoordinate: () => resolveCoordinate,
3509
3864
  resolveOperand: () => resolveOperand,
3510
3865
  segmentIntersection: () => segmentIntersection,
3511
3866
  signedArea: () => signedArea,
3867
+ snapBox: () => snapBox,
3868
+ snapToGridStep: () => snapToGridStep,
3869
+ snapValue: () => snapValue,
3512
3870
  subtractPolygons: () => subtractPolygons,
3513
3871
  subtractShapes: () => subtractShapes,
3514
3872
  subtractSvgPaths: () => subtractSvgPaths,
@@ -3518,6 +3876,7 @@ __export(geometry_exports, {
3518
3876
  unionPolygons: () => unionPolygons,
3519
3877
  unionShapes: () => unionShapes,
3520
3878
  unionSvgPaths: () => unionSvgPaths,
3879
+ usesSlideReference: () => usesSlideReference,
3521
3880
  vec2Eq: () => vec2Eq
3522
3881
  });
3523
3882
 
@@ -18608,6 +18967,312 @@ function flattenSvgPath(d, curveSegments = 16) {
18608
18967
  return loops;
18609
18968
  }
18610
18969
 
18970
+ // geometry/snap-guides.ts
18971
+ var SNAP_THRESHOLD = 6;
18972
+ function computeSnapToShape(dragX, dragY, dragW, dragH, siblings, draggedIds, guides, threshold = SNAP_THRESHOLD) {
18973
+ let bestDx = Infinity;
18974
+ let bestDy = Infinity;
18975
+ let snapX = dragX;
18976
+ let snapY = dragY;
18977
+ const lines = [];
18978
+ const dragCx = dragX + dragW / 2;
18979
+ const dragCy = dragY + dragH / 2;
18980
+ const dragRight = dragX + dragW;
18981
+ const dragBottom = dragY + dragH;
18982
+ const hRefs = [dragX, dragCx, dragRight];
18983
+ const vRefs = [dragY, dragCy, dragBottom];
18984
+ for (const sib of siblings) {
18985
+ if (draggedIds.has(sib.id)) {
18986
+ continue;
18987
+ }
18988
+ const sibCx = sib.x + sib.width / 2;
18989
+ const sibCy = sib.y + sib.height / 2;
18990
+ const sibRight = sib.x + sib.width;
18991
+ const sibBottom = sib.y + sib.height;
18992
+ for (const ref of [sib.x, sibCx, sibRight]) {
18993
+ for (const hr of hRefs) {
18994
+ const dx = Math.abs(ref - hr);
18995
+ if (dx < threshold && dx < bestDx) {
18996
+ bestDx = dx;
18997
+ snapX = dragX + (ref - hr);
18998
+ }
18999
+ }
19000
+ }
19001
+ for (const ref of [sib.y, sibCy, sibBottom]) {
19002
+ for (const vr of vRefs) {
19003
+ const dy = Math.abs(ref - vr);
19004
+ if (dy < threshold && dy < bestDy) {
19005
+ bestDy = dy;
19006
+ snapY = dragY + (ref - vr);
19007
+ }
19008
+ }
19009
+ }
19010
+ }
19011
+ for (const guide of guides) {
19012
+ if (guide.axis === "v") {
19013
+ for (const hr of hRefs) {
19014
+ const dx = Math.abs(guide.position - hr);
19015
+ if (dx < threshold && dx < bestDx) {
19016
+ bestDx = dx;
19017
+ snapX = dragX + (guide.position - hr);
19018
+ }
19019
+ }
19020
+ } else {
19021
+ for (const vr of vRefs) {
19022
+ const dy = Math.abs(guide.position - vr);
19023
+ if (dy < threshold && dy < bestDy) {
19024
+ bestDy = dy;
19025
+ snapY = dragY + (guide.position - vr);
19026
+ }
19027
+ }
19028
+ }
19029
+ }
19030
+ if (bestDx < threshold) {
19031
+ const snappedCx = snapX + dragW / 2;
19032
+ const snappedRight = snapX + dragW;
19033
+ for (const sib of siblings) {
19034
+ if (draggedIds.has(sib.id)) {
19035
+ continue;
19036
+ }
19037
+ for (const ref of [sib.x, sib.x + sib.width / 2, sib.x + sib.width]) {
19038
+ if (Math.abs(ref - snapX) < 1 || Math.abs(ref - snappedCx) < 1 || Math.abs(ref - snappedRight) < 1) {
19039
+ lines.push({ axis: "v", position: ref });
19040
+ }
19041
+ }
19042
+ }
19043
+ for (const g of guides) {
19044
+ if (g.axis === "v" && (Math.abs(g.position - snapX) < 1 || Math.abs(g.position - snappedCx) < 1 || Math.abs(g.position - snappedRight) < 1)) {
19045
+ lines.push({ axis: "v", position: g.position });
19046
+ }
19047
+ }
19048
+ }
19049
+ if (bestDy < threshold) {
19050
+ const snappedCy = snapY + dragH / 2;
19051
+ const snappedBottom = snapY + dragH;
19052
+ for (const sib of siblings) {
19053
+ if (draggedIds.has(sib.id)) {
19054
+ continue;
19055
+ }
19056
+ for (const ref of [sib.y, sib.y + sib.height / 2, sib.y + sib.height]) {
19057
+ if (Math.abs(ref - snapY) < 1 || Math.abs(ref - snappedCy) < 1 || Math.abs(ref - snappedBottom) < 1) {
19058
+ lines.push({ axis: "h", position: ref });
19059
+ }
19060
+ }
19061
+ }
19062
+ for (const g of guides) {
19063
+ if (g.axis === "h" && (Math.abs(g.position - snapY) < 1 || Math.abs(g.position - snappedCy) < 1 || Math.abs(g.position - snappedBottom) < 1)) {
19064
+ lines.push({ axis: "h", position: g.position });
19065
+ }
19066
+ }
19067
+ }
19068
+ return { x: snapX, y: snapY, lines };
19069
+ }
19070
+ function xCandidates(box) {
19071
+ return {
19072
+ leading: box.x,
19073
+ centre: box.x + box.width / 2,
19074
+ trailing: box.x + box.width
19075
+ };
19076
+ }
19077
+ function yCandidates(box) {
19078
+ return {
19079
+ leading: box.y,
19080
+ centre: box.y + box.height / 2,
19081
+ trailing: box.y + box.height
19082
+ };
19083
+ }
19084
+ function findSnapHit(movingValues, others, axisCandidates, threshold) {
19085
+ let best = null;
19086
+ let bestDist = threshold + 1;
19087
+ for (const other of others) {
19088
+ const target = axisCandidates(other);
19089
+ const targetValues = [target.leading, target.centre, target.trailing];
19090
+ for (const mv of movingValues) {
19091
+ for (const tv of targetValues) {
19092
+ const dist = mv > tv ? mv - tv : tv - mv;
19093
+ if (dist <= threshold && dist < bestDist) {
19094
+ bestDist = dist;
19095
+ best = { delta: tv - mv, matchedLine: tv, otherBox: other };
19096
+ }
19097
+ }
19098
+ }
19099
+ }
19100
+ return best;
19101
+ }
19102
+ function unionExtent(aStart, aEnd, bStart, bEnd) {
19103
+ const start = aStart < bStart ? aStart : bStart;
19104
+ const end = aEnd > bEnd ? aEnd : bEnd;
19105
+ return { start, end };
19106
+ }
19107
+ function computeSnap(box, others, threshold) {
19108
+ const guides = [];
19109
+ let snappedX = box.x;
19110
+ let snappedY = box.y;
19111
+ const xMoving = xCandidates(box);
19112
+ const hitX = findSnapHit(
19113
+ [xMoving.leading, xMoving.centre, xMoving.trailing],
19114
+ others,
19115
+ xCandidates,
19116
+ threshold
19117
+ );
19118
+ if (hitX !== null) {
19119
+ snappedX = box.x + hitX.delta;
19120
+ const { start, end } = unionExtent(
19121
+ box.y,
19122
+ box.y + box.height,
19123
+ hitX.otherBox.y,
19124
+ hitX.otherBox.y + hitX.otherBox.height
19125
+ );
19126
+ guides.push({ axis: "x", pos: hitX.matchedLine, start, end });
19127
+ }
19128
+ const yMoving = yCandidates(box);
19129
+ const hitY = findSnapHit(
19130
+ [yMoving.leading, yMoving.centre, yMoving.trailing],
19131
+ others,
19132
+ yCandidates,
19133
+ threshold
19134
+ );
19135
+ if (hitY !== null) {
19136
+ snappedY = box.y + hitY.delta;
19137
+ const { start, end } = unionExtent(
19138
+ snappedX,
19139
+ snappedX + box.width,
19140
+ hitY.otherBox.x,
19141
+ hitY.otherBox.x + hitY.otherBox.width
19142
+ );
19143
+ guides.push({ axis: "y", pos: hitY.matchedLine, start, end });
19144
+ }
19145
+ return { x: snappedX, y: snappedY, guides };
19146
+ }
19147
+ function snapToGridStep(value, step) {
19148
+ if (step <= 0) {
19149
+ return value;
19150
+ }
19151
+ return Math.round(value / step) * step || 0;
19152
+ }
19153
+ function snapValue(value, size) {
19154
+ return Math.round(value / size) * size;
19155
+ }
19156
+ function snapBox(box, size) {
19157
+ return {
19158
+ x: snapValue(box.x, size),
19159
+ y: snapValue(box.y, size),
19160
+ width: Math.max(size, snapValue(box.width, size)),
19161
+ height: Math.max(size, snapValue(box.height, size))
19162
+ };
19163
+ }
19164
+
19165
+ // geometry/align-distribute.ts
19166
+ function usesSlideReference(elementCount, options) {
19167
+ if (!options?.slideSize) {
19168
+ return false;
19169
+ }
19170
+ return options.reference === "slide" || options.reference === void 0 && elementCount === 1;
19171
+ }
19172
+ function resolveAlignReferenceBox(elements2, options) {
19173
+ if (elements2.length === 0) {
19174
+ return null;
19175
+ }
19176
+ if (usesSlideReference(elements2.length, options)) {
19177
+ return { minX: 0, minY: 0, maxX: options.slideSize.width, maxY: options.slideSize.height };
19178
+ }
19179
+ if (elements2.length < 2) {
19180
+ return null;
19181
+ }
19182
+ return {
19183
+ minX: Math.min(...elements2.map((e) => e.x)),
19184
+ minY: Math.min(...elements2.map((e) => e.y)),
19185
+ maxX: Math.max(...elements2.map((e) => e.x + e.width)),
19186
+ maxY: Math.max(...elements2.map((e) => e.y + e.height))
19187
+ };
19188
+ }
19189
+ function alignElements(elements2, edge, options) {
19190
+ const result = /* @__PURE__ */ new Map();
19191
+ const ref = resolveAlignReferenceBox(elements2, options);
19192
+ if (!ref) {
19193
+ return result;
19194
+ }
19195
+ const { minX, minY, maxX, maxY } = ref;
19196
+ const centerX = (minX + maxX) / 2;
19197
+ const centerY = (minY + maxY) / 2;
19198
+ for (const el of elements2) {
19199
+ switch (edge) {
19200
+ case "left":
19201
+ result.set(el.id, { x: minX });
19202
+ break;
19203
+ case "right":
19204
+ result.set(el.id, { x: maxX - el.width });
19205
+ break;
19206
+ case "centerH":
19207
+ result.set(el.id, { x: centerX - el.width / 2 });
19208
+ break;
19209
+ case "top":
19210
+ result.set(el.id, { y: minY });
19211
+ break;
19212
+ case "bottom":
19213
+ result.set(el.id, { y: maxY - el.height });
19214
+ break;
19215
+ case "middle":
19216
+ result.set(el.id, { y: centerY - el.height / 2 });
19217
+ break;
19218
+ }
19219
+ }
19220
+ return result;
19221
+ }
19222
+ function distributeElements(elements2, axis, options) {
19223
+ const result = /* @__PURE__ */ new Map();
19224
+ const horizontal = axis === "horizontal";
19225
+ const start = (e) => horizontal ? e.x : e.y;
19226
+ const size = (e) => horizontal ? e.width : e.height;
19227
+ const sorted = [...elements2].sort((a, b) => start(a) - start(b));
19228
+ const totalSize = sorted.reduce((sum, e) => sum + size(e), 0);
19229
+ let cursor;
19230
+ let gap;
19231
+ if (usesSlideReference(elements2.length, options)) {
19232
+ if (sorted.length === 0) {
19233
+ return result;
19234
+ }
19235
+ const span = horizontal ? options.slideSize.width : options.slideSize.height;
19236
+ gap = (span - totalSize) / (sorted.length + 1);
19237
+ cursor = gap;
19238
+ } else {
19239
+ if (sorted.length < 3) {
19240
+ return result;
19241
+ }
19242
+ const first2 = sorted[0];
19243
+ const last = sorted[sorted.length - 1];
19244
+ const span = start(last) + size(last) - start(first2);
19245
+ gap = (span - totalSize) / (sorted.length - 1);
19246
+ cursor = start(first2);
19247
+ }
19248
+ for (const el of sorted) {
19249
+ result.set(el.id, horizontal ? { x: cursor } : { y: cursor });
19250
+ cursor += size(el) + gap;
19251
+ }
19252
+ return result;
19253
+ }
19254
+ function onlyMoved(boxes, positions) {
19255
+ const byId = new Map(boxes.map((box) => [box.id, box]));
19256
+ const result = /* @__PURE__ */ new Map();
19257
+ for (const [id, pos] of positions) {
19258
+ const box = byId.get(id);
19259
+ if (!box) {
19260
+ continue;
19261
+ }
19262
+ const moved = pos.x !== void 0 && pos.x !== box.x || pos.y !== void 0 && pos.y !== box.y;
19263
+ if (moved) {
19264
+ result.set(id, pos);
19265
+ }
19266
+ }
19267
+ return result;
19268
+ }
19269
+ function computeAlign(boxes, mode, options) {
19270
+ return onlyMoved(boxes, alignElements(boxes, mode, options));
19271
+ }
19272
+ function computeDistribute(boxes, mode, options) {
19273
+ return onlyMoved(boxes, distributeElements(boxes, mode, options));
19274
+ }
19275
+
18611
19276
  // units/index.ts
18612
19277
  var units_exports = {};
18613
19278
  __export(units_exports, {