@mark1russell7/packing 0.1.7 → 0.2.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/build/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_TOLERANCE, EPS, RECORD_EPS, boundsOf, boundsOf as boundsOf$1, checkInput, marginSides, regionHitsRect, translate, turnBox, turnPoint, turnPort, turnRegion } from "@mark1russell7/layout-contract";
1
2
  // src/vector.ts
2
3
  /** A mutable two-dimensional vector. */
3
4
  var Vector = class Vector {
@@ -599,24 +600,9 @@ function pushCascade(placed, rects, options = {}) {
599
600
  return [...moved];
600
601
  }
601
602
  // src/contract.ts
602
- /** The tolerance for the comparison of two numbers in an engine. */
603
- const EPS = 1e-6;
604
- /** The tolerance for the comparison of two numbers of a file. The files round to 3 decimals. */
605
- const RECORD_EPS = .001;
606
- /** The default tolerances of the hard rules. Each record writes its tolerances in its top-level field `tolerance`. */
607
- const DEFAULT_TOLERANCE = Object.freeze({
608
- overlap: .001,
609
- through: .5
610
- });
611
603
  /** This function gives the margin of an item on each side. A number is the same margin on each side. */
612
604
  function marginOf(item) {
613
- const m = item.margin ?? 0;
614
- if (typeof m === "number") return {
615
- top: m,
616
- right: m,
617
- bottom: m,
618
- left: m
619
- };
605
+ const m = marginSides(item.margin);
620
606
  return {
621
607
  top: m.top,
622
608
  right: m.right,
@@ -624,94 +610,14 @@ function marginOf(item) {
624
610
  left: m.left
625
611
  };
626
612
  }
627
- /** The side of a port after a turn of 90 degrees clockwise. */
628
- const TURNED_SIDE = {
629
- top: "right",
630
- right: "bottom",
631
- bottom: "left",
632
- left: "top"
633
- };
634
- /** This function gives the point `px`, `py` of an item of height `h` after a turn of 90 degrees clockwise. */
635
- function turnPoint(px, py, h) {
636
- return [h - py, px];
637
- }
638
- /** This function turns a port of an item of height `h` by 90 degrees clockwise. The side turns from top to right. */
639
- function turnPort(port, h) {
640
- const turned = {
641
- id: port.id,
642
- side: TURNED_SIDE[port.side]
643
- };
644
- if (port.x !== void 0 && port.y !== void 0) {
645
- const [x, y] = turnPoint(port.x, port.y, h);
646
- turned.x = x;
647
- turned.y = y;
648
- } else if (port.x !== void 0) turned.y = port.x;
649
- else if (port.y !== void 0) turned.x = h - port.y;
650
- return turned;
651
- }
652
- /** This function turns a region relative to an item of height `h` by 90 degrees clockwise. */
653
- function turnRegion(region, h) {
654
- switch (region.kind) {
655
- case "rect": return {
656
- kind: "rect",
657
- x: h - region.y - region.h,
658
- y: region.x,
659
- w: region.h,
660
- h: region.w
661
- };
662
- case "circle": {
663
- const [cx, cy] = turnPoint(region.cx, region.cy, h);
664
- return {
665
- kind: "circle",
666
- cx,
667
- cy,
668
- r: region.r
669
- };
670
- }
671
- case "ellipse": {
672
- const [cx, cy] = turnPoint(region.cx, region.cy, h);
673
- return {
674
- kind: "ellipse",
675
- cx,
676
- cy,
677
- rx: region.ry,
678
- ry: region.rx
679
- };
680
- }
681
- case "polygon": {
682
- const turnRing = (ring) => ring.map(([x, y]) => turnPoint(x, y, h));
683
- const turned = {
684
- kind: "polygon",
685
- points: turnRing(region.points)
686
- };
687
- if (region.holes) turned.holes = region.holes.map(turnRing);
688
- return turned;
689
- }
690
- }
691
- }
692
613
  /**
693
614
  * This function gives an item after a turn of 90 degrees clockwise, with the y axis down (decision 1 of
694
615
  * `docs/PACKER.md`). The width and the height change places. The top-left corner, the id and the other fields stay.
695
616
  * The ports, the shape and the margin turn with the item. The function does not change its argument.
617
+ * It is `turnBox` of the contract.
696
618
  */
697
619
  function turnItem(item) {
698
- const turned = {
699
- ...item,
700
- w: item.h,
701
- h: item.w
702
- };
703
- if (item.ports) turned.ports = item.ports.map((p) => turnPort(p, item.h));
704
- if (item.shape) turned.shape = turnRegion(item.shape, item.h);
705
- if (item.margin !== void 0 && typeof item.margin !== "number") {
706
- const m = item.margin;
707
- turned.margin = {
708
- top: m.left,
709
- right: m.top,
710
- bottom: m.right,
711
- left: m.bottom
712
- };
713
- }
714
- return turned;
620
+ return turnBox(item);
715
621
  }
716
622
  // src/sha256.ts
717
623
  /** The round constants of SHA-256. */
@@ -888,8 +794,7 @@ function canonicalJson(value) {
888
794
  }
889
795
  /** This function gives the hash of an input file: the first 16 hex characters of the SHA-256 of its canonical form without `hash`. */
890
796
  function hashInput(file) {
891
- const rest = { ...file };
892
- delete rest.hash;
797
+ const { hash: _hash, ...rest } = file;
893
798
  return sha256Hex(canonicalJson(rest)).slice(0, 16);
894
799
  }
895
800
  /** This function gives the scale of a count: the nearest of 1, 10, 100, 1000 and 10000 on a log scale. The count 0 gives `1`. */
@@ -900,15 +805,17 @@ function countCell(count) {
900
805
  if (count <= 3162) return "1k";
901
806
  return "10k";
902
807
  }
903
- /** This function gives the packing input of a file. The boxes become the items, and the edges go. */
808
+ /**
809
+ * This function gives the packing input of a file. The boxes become the items, and the edges go.
810
+ * A file without a container gets `{ fixed: "none" }`, so the packer picks the size. It is `toPackInput` of the contract.
811
+ */
904
812
  function toPackInput(file) {
905
- const input = {
906
- container: file.container,
907
- items: file.boxes
813
+ return {
814
+ container: file.container ?? { fixed: "none" },
815
+ items: file.boxes,
816
+ ...file.obstacles === void 0 ? {} : { obstacles: file.obstacles },
817
+ ...file.gap === void 0 ? {} : { gap: file.gap }
908
818
  };
909
- if (file.obstacles !== void 0) input.obstacles = file.obstacles;
910
- if (file.gap !== void 0) input.gap = file.gap;
911
- return input;
912
819
  }
913
820
  // src/measures.ts
914
821
  const spec = (id, family, unit, direction, min, max, guard) => ({
@@ -1030,13 +937,13 @@ function boxesOf$1(input, output) {
1030
937
  * `RECORD_EPS` are on one line, and the left box comes first. Otherwise the upper box comes first.
1031
938
  */
1032
939
  function readsBefore(a, b) {
1033
- if (Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) > .001) return a.x + RECORD_EPS < b.x;
940
+ if (Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) > RECORD_EPS) return a.x + RECORD_EPS < b.x;
1034
941
  return a.y < b.y;
1035
942
  }
1036
943
  /** This function sums one measure over the stages of a trace. A trace without the measure gives 0. */
1037
944
  function traceSum(output, id) {
1038
945
  let sum = 0;
1039
- for (const stage of output.trace) sum += stage.measures[id] ?? 0;
946
+ for (const stage of output.trace ?? []) sum += stage.measures?.[id] ?? 0;
1040
947
  return sum;
1041
948
  }
1042
949
  /**
@@ -1122,7 +1029,7 @@ function measureChange(baseInput, baseOutput, changeInput, changeOutput) {
1122
1029
  if (q === void 0) continue;
1123
1030
  const dx = q.x - p.x;
1124
1031
  const dy = q.y - p.y;
1125
- if (Math.abs(dx) > .001 || Math.abs(dy) > .001 || (p.turn ?? 0) !== (q.turn ?? 0)) moved += 1;
1032
+ if (Math.abs(dx) > RECORD_EPS || Math.abs(dy) > RECORD_EPS || (p.turn ?? 0) !== (q.turn ?? 0)) moved += 1;
1126
1033
  distances.push(Math.hypot(dx, dy) / unit);
1127
1034
  }
1128
1035
  if (distances.length === 0) return {
@@ -1405,41 +1312,6 @@ const clock = globalThis.performance;
1405
1312
  function now() {
1406
1313
  return clock ? clock.now() : Date.now();
1407
1314
  }
1408
- /** This function gives the bounding box of a region. */
1409
- function boundsOf(region) {
1410
- switch (region.kind) {
1411
- case "rect": return {
1412
- x: region.x,
1413
- y: region.y,
1414
- w: region.w,
1415
- h: region.h
1416
- };
1417
- case "circle": return {
1418
- x: region.cx - region.r,
1419
- y: region.cy - region.r,
1420
- w: 2 * region.r,
1421
- h: 2 * region.r
1422
- };
1423
- case "ellipse": return {
1424
- x: region.cx - region.rx,
1425
- y: region.cy - region.ry,
1426
- w: 2 * region.rx,
1427
- h: 2 * region.ry
1428
- };
1429
- case "polygon": {
1430
- const xs = region.points.map((p) => p[0]);
1431
- const ys = region.points.map((p) => p[1]);
1432
- const x = Math.min(...xs);
1433
- const y = Math.min(...ys);
1434
- return {
1435
- x,
1436
- y,
1437
- w: Math.max(...xs) - x,
1438
- h: Math.max(...ys) - y
1439
- };
1440
- }
1441
- }
1442
- }
1443
1315
  /** This function gives the rectangle of a container. A rectangle shape gives the origin and the sizes of the fixed sides. */
1444
1316
  function containerBounds(input) {
1445
1317
  const { container } = input;
@@ -1487,7 +1359,7 @@ function buildFrame(input, movable = false) {
1487
1359
  if (item.locked === true && item.x !== void 0 && item.y !== void 0) {
1488
1360
  rect.locked = true;
1489
1361
  rect.position.set(item.x - dx - ox, item.y - dy - oy);
1490
- } else if (rect.size.x > w + 1e-6 || rect.size.y > h + 1e-6) {
1362
+ } else if (rect.size.x > w + EPS || rect.size.y > h + EPS) {
1491
1363
  tooLarge.push(item.id);
1492
1364
  continue;
1493
1365
  }
@@ -1616,59 +1488,55 @@ function transposeRegion(region) {
1616
1488
  };
1617
1489
  case "polygon": {
1618
1490
  const swap = (ring) => ring.map(([x, y]) => [y, x]);
1619
- const turned = {
1491
+ return region.holes === void 0 ? {
1620
1492
  kind: "polygon",
1621
1493
  points: swap(region.points)
1494
+ } : {
1495
+ kind: "polygon",
1496
+ points: swap(region.points),
1497
+ holes: region.holes.map(swap)
1622
1498
  };
1623
- if (region.holes) turned.holes = region.holes.map(swap);
1624
- return turned;
1625
1499
  }
1626
1500
  }
1627
1501
  }
1628
1502
  /** This function gives an item with the axes changed. The ports go, because the adapters of 0.1 do not read them. */
1629
1503
  function transposeItem(item) {
1630
- const { ports: _ports, ...rest } = item;
1631
- const turned = {
1504
+ const { ports: _ports, x, y, shape, margin, ...rest } = item;
1505
+ return {
1632
1506
  ...rest,
1633
1507
  w: item.h,
1634
- h: item.w
1508
+ h: item.w,
1509
+ ...y === void 0 ? {} : { x: y },
1510
+ ...x === void 0 ? {} : { y: x },
1511
+ ...shape === void 0 ? {} : { shape: transposeRegion(shape) },
1512
+ ...margin === void 0 ? {} : { margin: typeof margin === "number" ? margin : {
1513
+ top: margin.left,
1514
+ right: margin.bottom,
1515
+ bottom: margin.right,
1516
+ left: margin.top
1517
+ } }
1635
1518
  };
1636
- delete turned.x;
1637
- delete turned.y;
1638
- if (item.y !== void 0) turned.x = item.y;
1639
- if (item.x !== void 0) turned.y = item.x;
1640
- if (item.shape) turned.shape = transposeRegion(item.shape);
1641
- if (item.margin !== void 0 && typeof item.margin !== "number") {
1642
- const m = item.margin;
1643
- turned.margin = {
1644
- top: m.left,
1645
- right: m.bottom,
1646
- bottom: m.right,
1647
- left: m.top
1648
- };
1649
- }
1650
- return turned;
1651
1519
  }
1652
1520
  /** This function gives an input with the axes changed. A fixed height becomes a fixed width. */
1653
1521
  function transposeInput(input) {
1654
1522
  const c = input.container;
1655
- const container = { fixed: c.fixed === "height" ? "width" : c.fixed === "width" ? "height" : c.fixed };
1656
- if (c.h !== void 0) container.w = c.h;
1657
- if (c.w !== void 0) container.h = c.w;
1658
- if (c.aspect !== void 0) container.aspect = 1 / c.aspect;
1659
- if (c.aspectWeight !== void 0) container.aspectWeight = c.aspectWeight;
1660
- if (c.shape !== void 0) container.shape = transposeRegion(c.shape);
1661
- if (c.inset !== void 0) container.inset = c.inset;
1662
- const turned = {
1663
- container,
1664
- items: input.items.map(transposeItem)
1523
+ return {
1524
+ container: {
1525
+ fixed: c.fixed === "height" ? "width" : c.fixed === "width" ? "height" : c.fixed,
1526
+ ...c.h === void 0 ? {} : { w: c.h },
1527
+ ...c.w === void 0 ? {} : { h: c.w },
1528
+ ...c.aspect === void 0 ? {} : { aspect: 1 / c.aspect },
1529
+ ...c.aspectWeight === void 0 ? {} : { aspectWeight: c.aspectWeight },
1530
+ ...c.shape === void 0 ? {} : { shape: transposeRegion(c.shape) },
1531
+ ...c.inset === void 0 ? {} : { inset: c.inset }
1532
+ },
1533
+ items: input.items.map(transposeItem),
1534
+ ...input.obstacles === void 0 ? {} : { obstacles: input.obstacles.map((o) => ({
1535
+ ...o,
1536
+ region: transposeRegion(o.region)
1537
+ })) },
1538
+ ...input.gap === void 0 ? {} : { gap: input.gap }
1665
1539
  };
1666
- if (input.obstacles) turned.obstacles = input.obstacles.map((o) => ({
1667
- ...o,
1668
- region: transposeRegion(o.region)
1669
- }));
1670
- if (input.gap !== void 0) turned.gap = input.gap;
1671
- return turned;
1672
1540
  }
1673
1541
  /** This function gives an output with the axes changed. */
1674
1542
  function transposeOutput(output) {
@@ -1688,7 +1556,7 @@ function transposeOutput(output) {
1688
1556
  x: m.y,
1689
1557
  y: m.x
1690
1558
  })),
1691
- trace: output.trace
1559
+ ...output.trace === void 0 ? {} : { trace: output.trace }
1692
1560
  };
1693
1561
  }
1694
1562
  /**
@@ -1698,13 +1566,14 @@ function transposeOutput(output) {
1698
1566
  function viaAxes(input, previous, run) {
1699
1567
  const start = now();
1700
1568
  const inner = run(transposeInput(input), previous === void 0 ? void 0 : transposeOutput(previous));
1701
- const output = transposeOutput(inner);
1702
- output.trace = [{
1703
- id: "axes",
1704
- ms: now() - start,
1705
- measures: {}
1706
- }, ...inner.trace];
1707
- return output;
1569
+ return {
1570
+ ...transposeOutput(inner),
1571
+ trace: [{
1572
+ id: "axes",
1573
+ ms: now() - start,
1574
+ measures: {}
1575
+ }, ...inner.trace ?? []]
1576
+ };
1708
1577
  }
1709
1578
  // src/adapters.ts
1710
1579
  /** The cells of `rows`, `skyline` and `shelf`, without the movement of the obstacles. */
@@ -1745,12 +1614,14 @@ function packFixedWidth(id, input, options) {
1745
1614
  sortOrder: options.sortOrder
1746
1615
  });
1747
1616
  const output = readFrame(input, frame, []);
1748
- output.trace.push({
1749
- id,
1750
- ms: now() - start,
1751
- measures: { "pack.overflow": output.overflow.length }
1752
- });
1753
- return output;
1617
+ return {
1618
+ ...output,
1619
+ trace: [{
1620
+ id,
1621
+ ms: now() - start,
1622
+ measures: { "pack.overflow": output.overflow.length }
1623
+ }]
1624
+ };
1754
1625
  }
1755
1626
  /** This function makes the strategy of one function of `pack`. */
1756
1627
  function layoutStrategy(id, family, catalog, cells) {
@@ -1840,24 +1711,26 @@ const logSweep = {
1840
1711
  const ms = now() - start;
1841
1712
  const runs = result.tried.length + 1;
1842
1713
  const output = readFrame(input, frame, []);
1843
- output.trace.push({
1844
- id: "log-sweep",
1845
- ms,
1846
- measures: {
1847
- "pack.search.widths": result.tried.length,
1848
- "pack.search.ms": ms
1849
- }
1850
- }, {
1851
- id,
1852
- ms: ms / runs,
1853
- measures: { "pack.overflow": output.overflow.length }
1854
- });
1855
- return output;
1714
+ return {
1715
+ ...output,
1716
+ trace: [{
1717
+ id: "log-sweep",
1718
+ ms,
1719
+ measures: {
1720
+ "pack.search.widths": result.tried.length,
1721
+ "pack.search.ms": ms
1722
+ }
1723
+ }, {
1724
+ id,
1725
+ ms: ms / runs,
1726
+ measures: { "pack.overflow": output.overflow.length }
1727
+ }]
1728
+ };
1856
1729
  }
1857
1730
  };
1858
1731
  /** This function tells if the insides of two rectangles overlap by more than `EPS`. */
1859
1732
  function overlaps(a, b) {
1860
- return Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x) > 1e-6 && Math.min(a.bottom(), b.bottom()) - Math.max(a.position.y, b.position.y) > 1e-6;
1733
+ return Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x) > EPS && Math.min(a.bottom(), b.bottom()) - Math.max(a.position.y, b.position.y) > EPS;
1861
1734
  }
1862
1735
  /** This function packs an input with a fixed size or a fixed width with a push cascade. */
1863
1736
  function pushFixedWidth(input, options, previous) {
@@ -1899,9 +1772,9 @@ function pushFixedWidth(input, options, previous) {
1899
1772
  const startAt = starts.get(e);
1900
1773
  const x = e.rect.position.x + e.dx + frame.ox;
1901
1774
  const y = e.rect.position.y + e.dy + frame.oy;
1902
- const b = boundsOf(e.obstacle.region);
1903
- if (x < bounds.x - 1e-6 || y < bounds.y - 1e-6 || x + b.w > bounds.x + bounds.w + 1e-6 || y + b.h > bounds.y + bounds.h + 1e-6 || lockedItems.some((l) => overlaps(l.rect, e.rect))) e.rect.position.set(startAt.x, startAt.y);
1904
- else if (Math.abs(e.rect.position.x - startAt.x) > 1e-6 || Math.abs(e.rect.position.y - startAt.y) > 1e-6) moved.push({
1775
+ const b = boundsOf$1(e.obstacle.region);
1776
+ if (x < bounds.x - EPS || y < bounds.y - EPS || x + b.w > bounds.x + bounds.w + EPS || y + b.h > bounds.y + bounds.h + EPS || lockedItems.some((l) => overlaps(l.rect, e.rect))) e.rect.position.set(startAt.x, startAt.y);
1777
+ else if (Math.abs(e.rect.position.x - startAt.x) > EPS || Math.abs(e.rect.position.y - startAt.y) > EPS) moved.push({
1905
1778
  id: e.obstacle.id,
1906
1779
  x,
1907
1780
  y
@@ -1911,7 +1784,7 @@ function pushFixedWidth(input, options, previous) {
1911
1784
  const kept = [...obstacles, ...lockedItems].map((e) => e.rect);
1912
1785
  for (const e of positioned) {
1913
1786
  const r = e.rect;
1914
- if (r.position.x >= -1e-6 && r.position.y >= -1e-6 && r.right() <= frame.w + 1e-6 && r.bottom() <= frame.h + 1e-6 && !kept.some((k) => overlaps(k, r))) kept.push(r);
1787
+ if (r.position.x >= -EPS && r.position.y >= -EPS && r.right() <= frame.w + EPS && r.bottom() <= frame.h + EPS && !kept.some((k) => overlaps(k, r))) kept.push(r);
1915
1788
  else pending.push(e);
1916
1789
  }
1917
1790
  const skylineStart = now();
@@ -1927,19 +1800,21 @@ function pushFixedWidth(input, options, previous) {
1927
1800
  packSkyline([...pending.map((e) => e.rect), ...kept], outerOf(frame), { sortOrder: options.sortOrder });
1928
1801
  }
1929
1802
  const output = readFrame(input, frame, [], moved);
1930
- output.trace.push({
1931
- id: "push",
1932
- ms: pushMs,
1933
- measures: { "pack.push.moved": moved.length }
1934
- }, {
1935
- id: "skyline",
1936
- ms: now() - skylineStart,
1937
- measures: {
1938
- "pack.push.replaced": pending.length,
1939
- "pack.overflow": output.overflow.length
1940
- }
1941
- });
1942
- return output;
1803
+ return {
1804
+ ...output,
1805
+ trace: [{
1806
+ id: "push",
1807
+ ms: pushMs,
1808
+ measures: { "pack.push.moved": moved.length }
1809
+ }, {
1810
+ id: "skyline",
1811
+ ms: now() - skylineStart,
1812
+ measures: {
1813
+ "pack.push.replaced": pending.length,
1814
+ "pack.overflow": output.overflow.length
1815
+ }
1816
+ }]
1817
+ };
1943
1818
  }
1944
1819
  /**
1945
1820
  * The strategy `push` uses `pushCascade`. Each item starts at its `x` and `y`, or at its place in `context.previous`.
@@ -1989,31 +1864,29 @@ const push = {
1989
1864
  */
1990
1865
  function fromRects(rects, outer) {
1991
1866
  const fixed = outer.fixedSize ? "size" : outer.fixedWidth ? "width" : "none";
1992
- const container = { fixed };
1993
- if (fixed !== "none") container.w = outer.size.x;
1994
- if (fixed === "size") container.h = outer.size.y;
1995
- if (outer.position.x !== 0 || outer.position.y !== 0) container.shape = {
1996
- kind: "rect",
1997
- x: outer.position.x,
1998
- y: outer.position.y,
1999
- w: outer.size.x,
2000
- h: outer.size.y
2001
- };
2002
1867
  return {
2003
- container,
2004
- items: rects.map((r, i) => {
2005
- const item = {
2006
- id: String(i),
2007
- w: r.size.x,
2008
- h: r.size.y
2009
- };
2010
- if (r.locked) {
2011
- item.locked = true;
2012
- item.x = r.position.x;
2013
- item.y = r.position.y;
2014
- }
2015
- return item;
2016
- })
1868
+ container: {
1869
+ fixed,
1870
+ ...fixed === "none" ? {} : { w: outer.size.x },
1871
+ ...fixed === "size" ? { h: outer.size.y } : {},
1872
+ ...outer.position.x !== 0 || outer.position.y !== 0 ? { shape: {
1873
+ kind: "rect",
1874
+ x: outer.position.x,
1875
+ y: outer.position.y,
1876
+ w: outer.size.x,
1877
+ h: outer.size.y
1878
+ } } : {}
1879
+ },
1880
+ items: rects.map((r, i) => ({
1881
+ id: String(i),
1882
+ w: r.size.x,
1883
+ h: r.size.y,
1884
+ ...r.locked ? {
1885
+ locked: true,
1886
+ x: r.position.x,
1887
+ y: r.position.y
1888
+ } : {}
1889
+ }))
2017
1890
  };
2018
1891
  }
2019
1892
  /**
@@ -2068,6 +1941,15 @@ function packWith(id, input, options, seed = 0) {
2068
1941
  function overlap(a, b, tolerance) {
2069
1942
  return Math.min(a.r, b.r) - Math.max(a.l, b.l) > tolerance && Math.min(a.b, b.b) - Math.max(a.t, b.t) > tolerance;
2070
1943
  }
1944
+ /** This function gives the box made smaller by a tolerance on each side, as a rectangle. */
1945
+ function inner(box, tolerance) {
1946
+ return {
1947
+ x: box.l + tolerance,
1948
+ y: box.t + tolerance,
1949
+ w: box.r - box.l - 2 * tolerance,
1950
+ h: box.b - box.t - 2 * tolerance
1951
+ };
1952
+ }
2071
1953
  /**
2072
1954
  * This function counts the breaks of the 4 hard rules of the packer for an output.
2073
1955
  *
@@ -2078,38 +1960,36 @@ function overlap(a, b, tolerance) {
2078
1960
  * - `complete`: each item with no placement that is not in the overflow. A locked item breaks `locked-kept` in its place.
2079
1961
  *
2080
1962
  * A locked item with no placement stays at its input position for `no-overlap` and `in-container`. A locked item with no
2081
- * `x` or no `y` in the input is not locked for these rules.
1963
+ * `x` or no `y` in the input is a bad input. The rules skip it, as `packHard` of the contract does. `checkInput` finds it.
2082
1964
  *
2083
- * The rules read a shape of the container, of an obstacle or of an item as its bounding box (decision 5). The bounding box
2084
- * of an item shape is relative to the position of the item. The result has no tolerance in it.
1965
+ * The test of an obstacle is exact: a circle, an ellipse and a polygon with holes count only their insides, as in `packHard`.
1966
+ * The rules read a shape of the container or of an item as its bounding box (decision 5). The bounding box of an item
1967
+ * shape is relative to the position of the item. The result has no tolerance in it.
2085
1968
  */
2086
1969
  function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
2087
1970
  const tol = tolerance.overlap;
2088
1971
  const placements = /* @__PURE__ */ new Map();
2089
1972
  for (const p of output.placements) if (!placements.has(p.id)) placements.set(p.id, p);
2090
1973
  const overflow = new Set(output.overflow);
2091
- const breaks = {
2092
- "no-overlap": 0,
2093
- "in-container": 0,
2094
- "locked-kept": 0,
2095
- complete: 0
2096
- };
1974
+ let lockedKept = 0;
1975
+ let complete = 0;
2097
1976
  const boxes = [];
2098
1977
  for (const item of input.items) {
2099
- const lockedAt = item.locked === true && item.x !== void 0 && item.y !== void 0 ? {
1978
+ if (item.locked === true && (item.x === void 0 || item.y === void 0)) continue;
1979
+ const lockedAt = item.locked === true ? {
2100
1980
  x: item.x,
2101
1981
  y: item.y
2102
1982
  } : void 0;
2103
1983
  let p = placements.get(item.id);
2104
1984
  if (p === void 0) {
2105
1985
  if (lockedAt === void 0) {
2106
- if (!overflow.has(item.id)) breaks.complete += 1;
1986
+ if (!overflow.has(item.id)) complete += 1;
2107
1987
  continue;
2108
1988
  }
2109
- breaks["locked-kept"] += 1;
1989
+ lockedKept += 1;
2110
1990
  p = lockedAt;
2111
1991
  } else if (lockedAt !== void 0) {
2112
- if (Math.abs(p.x - lockedAt.x) > .001 || Math.abs(p.y - lockedAt.y) > .001) breaks["locked-kept"] += 1;
1992
+ if (Math.abs(p.x - lockedAt.x) > RECORD_EPS || Math.abs(p.y - lockedAt.y) > RECORD_EPS) lockedKept += 1;
2113
1993
  }
2114
1994
  const placed = p.turn === 90 ? turnItem(item) : item;
2115
1995
  const m = marginOf(placed);
@@ -2130,18 +2010,17 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
2130
2010
  }
2131
2011
  const moved = new Map(output.moved.map((m) => [m.id, m]));
2132
2012
  const obstacles = (input.obstacles ?? []).map((o) => {
2013
+ const at = moved.get(o.id);
2014
+ if (at === void 0) return o.region;
2133
2015
  const b = boundsOf(o.region);
2134
- const at = moved.get(o.id) ?? b;
2135
- return {
2136
- l: at.x,
2137
- t: at.y,
2138
- r: at.x + b.w,
2139
- b: at.y + b.h
2140
- };
2016
+ return translate(o.region, at.x - b.x, at.y - b.y);
2141
2017
  });
2018
+ let overlaps = 0;
2142
2019
  for (const [i, a] of boxes.entries()) {
2143
- for (let j = i + 1; j < boxes.length; j++) if (overlap(a, boxes[j], tol)) breaks["no-overlap"] += 1;
2144
- for (const o of obstacles) if (overlap(a, o, tol)) breaks["no-overlap"] += 1;
2020
+ for (let j = i + 1; j < boxes.length; j++) if (overlap(a, boxes[j], tol)) overlaps += 1;
2021
+ if (obstacles.length === 0) continue;
2022
+ const rect = inner(a, tol);
2023
+ for (const region of obstacles) if (regionHitsRect(region, rect)) overlaps += 1;
2145
2024
  }
2146
2025
  const { container } = input;
2147
2026
  const shape = container.shape === void 0 ? void 0 : boundsOf(container.shape);
@@ -2152,12 +2031,18 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
2152
2031
  const inset = container.inset ?? 0;
2153
2032
  const fixedW = container.fixed === "size" || container.fixed === "width";
2154
2033
  const fixedH = container.fixed === "size" || container.fixed === "height";
2034
+ let outside = 0;
2155
2035
  for (const box of boxes) {
2156
2036
  const outX = fixedW && (box.l < x + inset - tol || box.r > x + w - inset + tol);
2157
2037
  const outY = fixedH && (box.t < y + inset - tol || box.b > y + h - inset + tol);
2158
- if (outX || outY) breaks["in-container"] += 1;
2038
+ if (outX || outY) outside += 1;
2159
2039
  }
2160
- return breaks;
2040
+ return {
2041
+ "no-overlap": overlaps,
2042
+ "in-container": outside,
2043
+ "locked-kept": lockedKept,
2044
+ complete
2045
+ };
2161
2046
  }
2162
2047
  // src/generators.ts
2163
2048
  /** The largest count of boxes that a generator makes. */
@@ -2378,8 +2263,10 @@ function containerOf(size, w, h, shape) {
2378
2263
  aspectWeight: 0
2379
2264
  }
2380
2265
  }[size];
2381
- if (shape !== void 0) container.shape = shape;
2382
- return container;
2266
+ return shape === void 0 ? container : {
2267
+ ...container,
2268
+ shape
2269
+ };
2383
2270
  }
2384
2271
  /** This function gives the 4 corners of a rectangle, clockwise from the top left. */
2385
2272
  const corners = (x, y, w, h) => [
@@ -3071,6 +2958,6 @@ function corpus(name, specs) {
3071
2958
  inputs
3072
2959
  };
3073
2960
  }
3074
- export { DEFAULT_TOLERANCE, EPS, MEASURE_SPECS, RECORD_EPS, Rect, SORT_ORDERS, UnsupportedCell, Vector, aspectError, assertSupported, canonicalJson, cards, cellOf, checkHard, compareRects, corpus, countCell, createContext, dashboard, edgeCases, fnv1a32, fromRects, gallery, generator, generators, hashInput, isDeterministic, logSweep, lowestWindow, marginOf, measure, measureChange, medianRatio, mulberry32, pack, packFree, packRows, packShelf, packSkyline, packWith, packingMetrics, pairedRatio, push, pushCascade, quantile, rectGrid, rectScattered, round3, roundMeasures, rows, shelf, skyline, strategies, strategy, supportsCell, tailShare, toPackInput, toRects, turnItem, turnPoint, turnPort, turnRegion, unsupportedOf, wordCloud };
2961
+ export { DEFAULT_TOLERANCE, EPS, MEASURE_SPECS, RECORD_EPS, Rect, SORT_ORDERS, UnsupportedCell, Vector, aspectError, assertSupported, canonicalJson, cards, cellOf, checkHard, checkInput, compareRects, corpus, countCell, createContext, dashboard, edgeCases, fnv1a32, fromRects, gallery, generator, generators, hashInput, isDeterministic, logSweep, lowestWindow, marginOf, measure, measureChange, medianRatio, mulberry32, pack, packFree, packRows, packShelf, packSkyline, packWith, packingMetrics, pairedRatio, push, pushCascade, quantile, rectGrid, rectScattered, round3, roundMeasures, rows, shelf, skyline, strategies, strategy, supportsCell, tailShare, toPackInput, toRects, turnItem, turnPoint, turnPort, turnRegion, unsupportedOf, wordCloud };
3075
2962
 
3076
2963
  //# sourceMappingURL=index.js.map