@mark1russell7/packing 0.1.6 → 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/README.md +1 -1
- package/build/index.d.ts +66 -215
- package/build/index.d.ts.map +1 -1
- package/build/index.js +210 -292
- package/build/index.js.map +1 -1
- package/package.json +5 -2
- package/src/adapters.ts +16 -13
- package/src/check.ts +44 -30
- package/src/contract.ts +30 -190
- package/src/convert.ts +11 -15
- package/src/corpus.ts +3 -3
- package/src/frame.ts +37 -50
- package/src/generators.ts +11 -12
- package/src/index.ts +7 -3
- package/src/layout-input.ts +34 -41
- package/src/measures.ts +43 -28
- package/src/strategy.ts +3 -3
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
|
|
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
|
-
|
|
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
|
|
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,39 +805,63 @@ function countCell(count) {
|
|
|
900
805
|
if (count <= 3162) return "1k";
|
|
901
806
|
return "10k";
|
|
902
807
|
}
|
|
903
|
-
/**
|
|
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
|
-
|
|
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
|
-
const spec = (id, family, unit, direction, min, max) => ({
|
|
821
|
+
const spec = (id, family, unit, direction, min, max, guard) => ({
|
|
915
822
|
id,
|
|
916
823
|
family,
|
|
917
824
|
unit,
|
|
918
825
|
direction,
|
|
919
826
|
min,
|
|
920
|
-
max
|
|
827
|
+
max,
|
|
828
|
+
...guard
|
|
921
829
|
});
|
|
922
|
-
/**
|
|
830
|
+
/**
|
|
831
|
+
* The facts of each measure, by id. `docs/MEASURES.md` gives the full definitions. This record is the source of the floor
|
|
832
|
+
* and the tolerance of each guard of the packer.
|
|
833
|
+
*/
|
|
923
834
|
const MEASURE_SPECS = Object.freeze({
|
|
924
835
|
"pack.area": spec("pack.area", "density", "square units", "lower", 0, Infinity),
|
|
925
836
|
"pack.fill": spec("pack.fill", "density", "share", "higher", 0, 1),
|
|
926
837
|
"pack.whitespace": spec("pack.whitespace", "density", "share", "lower", 0, 1),
|
|
927
838
|
"pack.aspect": spec("pack.aspect", "aspect", "ratio", "lower", 1, Infinity),
|
|
928
|
-
"pack.aspectError": spec("pack.aspectError", "aspect", "natural log", "lower", 0, Infinity
|
|
929
|
-
|
|
839
|
+
"pack.aspectError": spec("pack.aspectError", "aspect", "natural log", "lower", 0, Infinity, {
|
|
840
|
+
floor: 0,
|
|
841
|
+
tol: .01
|
|
842
|
+
}),
|
|
843
|
+
"pack.order.inversions": spec("pack.order.inversions", "order", "share", "lower", 0, 1, {
|
|
844
|
+
floor: 0,
|
|
845
|
+
tol: 0
|
|
846
|
+
}),
|
|
930
847
|
"pack.order.breaks": spec("pack.order.breaks", "order", "count", "lower", 0, Infinity),
|
|
931
848
|
"pack.raggedness": spec("pack.raggedness", "raggedness", "share", "lower", 0, 1),
|
|
932
|
-
"pack.overflow": spec("pack.overflow", "overflow", "count", "lower", 0, Infinity
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
849
|
+
"pack.overflow": spec("pack.overflow", "overflow", "count", "lower", 0, Infinity, {
|
|
850
|
+
floor: 0,
|
|
851
|
+
tol: 0
|
|
852
|
+
}),
|
|
853
|
+
"pack.stability.moved": spec("pack.stability.moved", "stability", "share", "lower", 0, 1, {
|
|
854
|
+
floor: 0,
|
|
855
|
+
tol: .02
|
|
856
|
+
}),
|
|
857
|
+
"pack.stability.mean": spec("pack.stability.mean", "stability", "container widths", "lower", 0, Infinity, {
|
|
858
|
+
floor: 0,
|
|
859
|
+
tol: .005
|
|
860
|
+
}),
|
|
861
|
+
"pack.stability.q95": spec("pack.stability.q95", "stability", "container widths", "lower", 0, Infinity, {
|
|
862
|
+
floor: 0,
|
|
863
|
+
tol: .01
|
|
864
|
+
}),
|
|
936
865
|
"pack.deterministic": spec("pack.deterministic", "determinism", "boolean", "higher", 0, 1),
|
|
937
866
|
"pack.search.widths": spec("pack.search.widths", "speed", "count", "lower", 0, Infinity),
|
|
938
867
|
"pack.search.ms": spec("pack.search.ms", "speed", "milliseconds", "lower", 0, Infinity)
|
|
@@ -1008,13 +937,13 @@ function boxesOf$1(input, output) {
|
|
|
1008
937
|
* `RECORD_EPS` are on one line, and the left box comes first. Otherwise the upper box comes first.
|
|
1009
938
|
*/
|
|
1010
939
|
function readsBefore(a, b) {
|
|
1011
|
-
if (Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) >
|
|
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;
|
|
1012
941
|
return a.y < b.y;
|
|
1013
942
|
}
|
|
1014
943
|
/** This function sums one measure over the stages of a trace. A trace without the measure gives 0. */
|
|
1015
944
|
function traceSum(output, id) {
|
|
1016
945
|
let sum = 0;
|
|
1017
|
-
for (const stage of output.trace) sum += stage.measures[id] ?? 0;
|
|
946
|
+
for (const stage of output.trace ?? []) sum += stage.measures?.[id] ?? 0;
|
|
1018
947
|
return sum;
|
|
1019
948
|
}
|
|
1020
949
|
/**
|
|
@@ -1100,7 +1029,7 @@ function measureChange(baseInput, baseOutput, changeInput, changeOutput) {
|
|
|
1100
1029
|
if (q === void 0) continue;
|
|
1101
1030
|
const dx = q.x - p.x;
|
|
1102
1031
|
const dy = q.y - p.y;
|
|
1103
|
-
if (Math.abs(dx) >
|
|
1032
|
+
if (Math.abs(dx) > RECORD_EPS || Math.abs(dy) > RECORD_EPS || (p.turn ?? 0) !== (q.turn ?? 0)) moved += 1;
|
|
1104
1033
|
distances.push(Math.hypot(dx, dy) / unit);
|
|
1105
1034
|
}
|
|
1106
1035
|
if (distances.length === 0) return {
|
|
@@ -1383,41 +1312,6 @@ const clock = globalThis.performance;
|
|
|
1383
1312
|
function now() {
|
|
1384
1313
|
return clock ? clock.now() : Date.now();
|
|
1385
1314
|
}
|
|
1386
|
-
/** This function gives the bounding box of a region. */
|
|
1387
|
-
function boundsOf(region) {
|
|
1388
|
-
switch (region.kind) {
|
|
1389
|
-
case "rect": return {
|
|
1390
|
-
x: region.x,
|
|
1391
|
-
y: region.y,
|
|
1392
|
-
w: region.w,
|
|
1393
|
-
h: region.h
|
|
1394
|
-
};
|
|
1395
|
-
case "circle": return {
|
|
1396
|
-
x: region.cx - region.r,
|
|
1397
|
-
y: region.cy - region.r,
|
|
1398
|
-
w: 2 * region.r,
|
|
1399
|
-
h: 2 * region.r
|
|
1400
|
-
};
|
|
1401
|
-
case "ellipse": return {
|
|
1402
|
-
x: region.cx - region.rx,
|
|
1403
|
-
y: region.cy - region.ry,
|
|
1404
|
-
w: 2 * region.rx,
|
|
1405
|
-
h: 2 * region.ry
|
|
1406
|
-
};
|
|
1407
|
-
case "polygon": {
|
|
1408
|
-
const xs = region.points.map((p) => p[0]);
|
|
1409
|
-
const ys = region.points.map((p) => p[1]);
|
|
1410
|
-
const x = Math.min(...xs);
|
|
1411
|
-
const y = Math.min(...ys);
|
|
1412
|
-
return {
|
|
1413
|
-
x,
|
|
1414
|
-
y,
|
|
1415
|
-
w: Math.max(...xs) - x,
|
|
1416
|
-
h: Math.max(...ys) - y
|
|
1417
|
-
};
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
1315
|
/** This function gives the rectangle of a container. A rectangle shape gives the origin and the sizes of the fixed sides. */
|
|
1422
1316
|
function containerBounds(input) {
|
|
1423
1317
|
const { container } = input;
|
|
@@ -1465,7 +1359,7 @@ function buildFrame(input, movable = false) {
|
|
|
1465
1359
|
if (item.locked === true && item.x !== void 0 && item.y !== void 0) {
|
|
1466
1360
|
rect.locked = true;
|
|
1467
1361
|
rect.position.set(item.x - dx - ox, item.y - dy - oy);
|
|
1468
|
-
} else if (rect.size.x > w +
|
|
1362
|
+
} else if (rect.size.x > w + EPS || rect.size.y > h + EPS) {
|
|
1469
1363
|
tooLarge.push(item.id);
|
|
1470
1364
|
continue;
|
|
1471
1365
|
}
|
|
@@ -1594,59 +1488,55 @@ function transposeRegion(region) {
|
|
|
1594
1488
|
};
|
|
1595
1489
|
case "polygon": {
|
|
1596
1490
|
const swap = (ring) => ring.map(([x, y]) => [y, x]);
|
|
1597
|
-
|
|
1491
|
+
return region.holes === void 0 ? {
|
|
1598
1492
|
kind: "polygon",
|
|
1599
1493
|
points: swap(region.points)
|
|
1494
|
+
} : {
|
|
1495
|
+
kind: "polygon",
|
|
1496
|
+
points: swap(region.points),
|
|
1497
|
+
holes: region.holes.map(swap)
|
|
1600
1498
|
};
|
|
1601
|
-
if (region.holes) turned.holes = region.holes.map(swap);
|
|
1602
|
-
return turned;
|
|
1603
1499
|
}
|
|
1604
1500
|
}
|
|
1605
1501
|
}
|
|
1606
1502
|
/** This function gives an item with the axes changed. The ports go, because the adapters of 0.1 do not read them. */
|
|
1607
1503
|
function transposeItem(item) {
|
|
1608
|
-
const { ports: _ports, ...rest } = item;
|
|
1609
|
-
|
|
1504
|
+
const { ports: _ports, x, y, shape, margin, ...rest } = item;
|
|
1505
|
+
return {
|
|
1610
1506
|
...rest,
|
|
1611
1507
|
w: item.h,
|
|
1612
|
-
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
|
+
} }
|
|
1613
1518
|
};
|
|
1614
|
-
delete turned.x;
|
|
1615
|
-
delete turned.y;
|
|
1616
|
-
if (item.y !== void 0) turned.x = item.y;
|
|
1617
|
-
if (item.x !== void 0) turned.y = item.x;
|
|
1618
|
-
if (item.shape) turned.shape = transposeRegion(item.shape);
|
|
1619
|
-
if (item.margin !== void 0 && typeof item.margin !== "number") {
|
|
1620
|
-
const m = item.margin;
|
|
1621
|
-
turned.margin = {
|
|
1622
|
-
top: m.left,
|
|
1623
|
-
right: m.bottom,
|
|
1624
|
-
bottom: m.right,
|
|
1625
|
-
left: m.top
|
|
1626
|
-
};
|
|
1627
|
-
}
|
|
1628
|
-
return turned;
|
|
1629
1519
|
}
|
|
1630
1520
|
/** This function gives an input with the axes changed. A fixed height becomes a fixed width. */
|
|
1631
1521
|
function transposeInput(input) {
|
|
1632
1522
|
const c = input.container;
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
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 }
|
|
1643
1539
|
};
|
|
1644
|
-
if (input.obstacles) turned.obstacles = input.obstacles.map((o) => ({
|
|
1645
|
-
...o,
|
|
1646
|
-
region: transposeRegion(o.region)
|
|
1647
|
-
}));
|
|
1648
|
-
if (input.gap !== void 0) turned.gap = input.gap;
|
|
1649
|
-
return turned;
|
|
1650
1540
|
}
|
|
1651
1541
|
/** This function gives an output with the axes changed. */
|
|
1652
1542
|
function transposeOutput(output) {
|
|
@@ -1666,7 +1556,7 @@ function transposeOutput(output) {
|
|
|
1666
1556
|
x: m.y,
|
|
1667
1557
|
y: m.x
|
|
1668
1558
|
})),
|
|
1669
|
-
trace: output.trace
|
|
1559
|
+
...output.trace === void 0 ? {} : { trace: output.trace }
|
|
1670
1560
|
};
|
|
1671
1561
|
}
|
|
1672
1562
|
/**
|
|
@@ -1676,13 +1566,14 @@ function transposeOutput(output) {
|
|
|
1676
1566
|
function viaAxes(input, previous, run) {
|
|
1677
1567
|
const start = now();
|
|
1678
1568
|
const inner = run(transposeInput(input), previous === void 0 ? void 0 : transposeOutput(previous));
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1569
|
+
return {
|
|
1570
|
+
...transposeOutput(inner),
|
|
1571
|
+
trace: [{
|
|
1572
|
+
id: "axes",
|
|
1573
|
+
ms: now() - start,
|
|
1574
|
+
measures: {}
|
|
1575
|
+
}, ...inner.trace ?? []]
|
|
1576
|
+
};
|
|
1686
1577
|
}
|
|
1687
1578
|
// src/adapters.ts
|
|
1688
1579
|
/** The cells of `rows`, `skyline` and `shelf`, without the movement of the obstacles. */
|
|
@@ -1723,12 +1614,14 @@ function packFixedWidth(id, input, options) {
|
|
|
1723
1614
|
sortOrder: options.sortOrder
|
|
1724
1615
|
});
|
|
1725
1616
|
const output = readFrame(input, frame, []);
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1617
|
+
return {
|
|
1618
|
+
...output,
|
|
1619
|
+
trace: [{
|
|
1620
|
+
id,
|
|
1621
|
+
ms: now() - start,
|
|
1622
|
+
measures: { "pack.overflow": output.overflow.length }
|
|
1623
|
+
}]
|
|
1624
|
+
};
|
|
1732
1625
|
}
|
|
1733
1626
|
/** This function makes the strategy of one function of `pack`. */
|
|
1734
1627
|
function layoutStrategy(id, family, catalog, cells) {
|
|
@@ -1818,24 +1711,26 @@ const logSweep = {
|
|
|
1818
1711
|
const ms = now() - start;
|
|
1819
1712
|
const runs = result.tried.length + 1;
|
|
1820
1713
|
const output = readFrame(input, frame, []);
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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
|
+
};
|
|
1834
1729
|
}
|
|
1835
1730
|
};
|
|
1836
1731
|
/** This function tells if the insides of two rectangles overlap by more than `EPS`. */
|
|
1837
1732
|
function overlaps(a, b) {
|
|
1838
|
-
return Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x) >
|
|
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;
|
|
1839
1734
|
}
|
|
1840
1735
|
/** This function packs an input with a fixed size or a fixed width with a push cascade. */
|
|
1841
1736
|
function pushFixedWidth(input, options, previous) {
|
|
@@ -1877,9 +1772,9 @@ function pushFixedWidth(input, options, previous) {
|
|
|
1877
1772
|
const startAt = starts.get(e);
|
|
1878
1773
|
const x = e.rect.position.x + e.dx + frame.ox;
|
|
1879
1774
|
const y = e.rect.position.y + e.dy + frame.oy;
|
|
1880
|
-
const b = boundsOf(e.obstacle.region);
|
|
1881
|
-
if (x < bounds.x -
|
|
1882
|
-
else if (Math.abs(e.rect.position.x - startAt.x) >
|
|
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({
|
|
1883
1778
|
id: e.obstacle.id,
|
|
1884
1779
|
x,
|
|
1885
1780
|
y
|
|
@@ -1889,7 +1784,7 @@ function pushFixedWidth(input, options, previous) {
|
|
|
1889
1784
|
const kept = [...obstacles, ...lockedItems].map((e) => e.rect);
|
|
1890
1785
|
for (const e of positioned) {
|
|
1891
1786
|
const r = e.rect;
|
|
1892
|
-
if (r.position.x >= -
|
|
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);
|
|
1893
1788
|
else pending.push(e);
|
|
1894
1789
|
}
|
|
1895
1790
|
const skylineStart = now();
|
|
@@ -1905,19 +1800,21 @@ function pushFixedWidth(input, options, previous) {
|
|
|
1905
1800
|
packSkyline([...pending.map((e) => e.rect), ...kept], outerOf(frame), { sortOrder: options.sortOrder });
|
|
1906
1801
|
}
|
|
1907
1802
|
const output = readFrame(input, frame, [], moved);
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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
|
+
};
|
|
1921
1818
|
}
|
|
1922
1819
|
/**
|
|
1923
1820
|
* The strategy `push` uses `pushCascade`. Each item starts at its `x` and `y`, or at its place in `context.previous`.
|
|
@@ -1967,31 +1864,29 @@ const push = {
|
|
|
1967
1864
|
*/
|
|
1968
1865
|
function fromRects(rects, outer) {
|
|
1969
1866
|
const fixed = outer.fixedSize ? "size" : outer.fixedWidth ? "width" : "none";
|
|
1970
|
-
const container = { fixed };
|
|
1971
|
-
if (fixed !== "none") container.w = outer.size.x;
|
|
1972
|
-
if (fixed === "size") container.h = outer.size.y;
|
|
1973
|
-
if (outer.position.x !== 0 || outer.position.y !== 0) container.shape = {
|
|
1974
|
-
kind: "rect",
|
|
1975
|
-
x: outer.position.x,
|
|
1976
|
-
y: outer.position.y,
|
|
1977
|
-
w: outer.size.x,
|
|
1978
|
-
h: outer.size.y
|
|
1979
|
-
};
|
|
1980
1867
|
return {
|
|
1981
|
-
container
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
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
|
+
}))
|
|
1995
1890
|
};
|
|
1996
1891
|
}
|
|
1997
1892
|
/**
|
|
@@ -2046,39 +1941,55 @@ function packWith(id, input, options, seed = 0) {
|
|
|
2046
1941
|
function overlap(a, b, tolerance) {
|
|
2047
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;
|
|
2048
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
|
+
}
|
|
2049
1953
|
/**
|
|
2050
1954
|
* This function counts the breaks of the 4 hard rules of the packer for an output.
|
|
2051
1955
|
*
|
|
2052
1956
|
* - `no-overlap`: each pair of items with their margins, and each pair of an item and an obstacle, that overlap by more than `tolerance.overlap`.
|
|
2053
1957
|
* - `in-container`: each item with its margin that goes out of a fixed side by more than `tolerance.overlap`. The inset of
|
|
2054
1958
|
* the container makes each fixed side smaller (decision 3 of `docs/PACKER.md`).
|
|
2055
|
-
* - `locked-kept`: each locked item that moved by more than `RECORD_EPS`, or that has no
|
|
2056
|
-
* - `complete`: each item with no
|
|
1959
|
+
* - `locked-kept`: each locked item that moved by more than `RECORD_EPS`, or that has no placement.
|
|
1960
|
+
* - `complete`: each item with no placement that is not in the overflow. A locked item breaks `locked-kept` in its place.
|
|
1961
|
+
*
|
|
1962
|
+
* A locked item with no placement stays at its input position for `no-overlap` and `in-container`. A locked item with no
|
|
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.
|
|
2057
1964
|
*
|
|
2058
|
-
* The
|
|
2059
|
-
*
|
|
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.
|
|
2060
1968
|
*/
|
|
2061
1969
|
function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
2062
1970
|
const tol = tolerance.overlap;
|
|
2063
1971
|
const placements = /* @__PURE__ */ new Map();
|
|
2064
1972
|
for (const p of output.placements) if (!placements.has(p.id)) placements.set(p.id, p);
|
|
2065
1973
|
const overflow = new Set(output.overflow);
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
"in-container": 0,
|
|
2069
|
-
"locked-kept": 0,
|
|
2070
|
-
complete: 0
|
|
2071
|
-
};
|
|
1974
|
+
let lockedKept = 0;
|
|
1975
|
+
let complete = 0;
|
|
2072
1976
|
const boxes = [];
|
|
2073
1977
|
for (const item of input.items) {
|
|
2074
|
-
|
|
1978
|
+
if (item.locked === true && (item.x === void 0 || item.y === void 0)) continue;
|
|
1979
|
+
const lockedAt = item.locked === true ? {
|
|
1980
|
+
x: item.x,
|
|
1981
|
+
y: item.y
|
|
1982
|
+
} : void 0;
|
|
1983
|
+
let p = placements.get(item.id);
|
|
2075
1984
|
if (p === void 0) {
|
|
2076
|
-
if (
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
1985
|
+
if (lockedAt === void 0) {
|
|
1986
|
+
if (!overflow.has(item.id)) complete += 1;
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
lockedKept += 1;
|
|
1990
|
+
p = lockedAt;
|
|
1991
|
+
} else if (lockedAt !== void 0) {
|
|
1992
|
+
if (Math.abs(p.x - lockedAt.x) > RECORD_EPS || Math.abs(p.y - lockedAt.y) > RECORD_EPS) lockedKept += 1;
|
|
2082
1993
|
}
|
|
2083
1994
|
const placed = p.turn === 90 ? turnItem(item) : item;
|
|
2084
1995
|
const m = marginOf(placed);
|
|
@@ -2099,18 +2010,17 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
|
2099
2010
|
}
|
|
2100
2011
|
const moved = new Map(output.moved.map((m) => [m.id, m]));
|
|
2101
2012
|
const obstacles = (input.obstacles ?? []).map((o) => {
|
|
2013
|
+
const at = moved.get(o.id);
|
|
2014
|
+
if (at === void 0) return o.region;
|
|
2102
2015
|
const b = boundsOf(o.region);
|
|
2103
|
-
|
|
2104
|
-
return {
|
|
2105
|
-
l: at.x,
|
|
2106
|
-
t: at.y,
|
|
2107
|
-
r: at.x + b.w,
|
|
2108
|
-
b: at.y + b.h
|
|
2109
|
-
};
|
|
2016
|
+
return translate(o.region, at.x - b.x, at.y - b.y);
|
|
2110
2017
|
});
|
|
2018
|
+
let overlaps = 0;
|
|
2111
2019
|
for (const [i, a] of boxes.entries()) {
|
|
2112
|
-
for (let j = i + 1; j < boxes.length; j++) if (overlap(a, boxes[j], tol))
|
|
2113
|
-
|
|
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;
|
|
2114
2024
|
}
|
|
2115
2025
|
const { container } = input;
|
|
2116
2026
|
const shape = container.shape === void 0 ? void 0 : boundsOf(container.shape);
|
|
@@ -2121,12 +2031,18 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
|
2121
2031
|
const inset = container.inset ?? 0;
|
|
2122
2032
|
const fixedW = container.fixed === "size" || container.fixed === "width";
|
|
2123
2033
|
const fixedH = container.fixed === "size" || container.fixed === "height";
|
|
2034
|
+
let outside = 0;
|
|
2124
2035
|
for (const box of boxes) {
|
|
2125
2036
|
const outX = fixedW && (box.l < x + inset - tol || box.r > x + w - inset + tol);
|
|
2126
2037
|
const outY = fixedH && (box.t < y + inset - tol || box.b > y + h - inset + tol);
|
|
2127
|
-
if (outX || outY)
|
|
2038
|
+
if (outX || outY) outside += 1;
|
|
2128
2039
|
}
|
|
2129
|
-
return
|
|
2040
|
+
return {
|
|
2041
|
+
"no-overlap": overlaps,
|
|
2042
|
+
"in-container": outside,
|
|
2043
|
+
"locked-kept": lockedKept,
|
|
2044
|
+
complete
|
|
2045
|
+
};
|
|
2130
2046
|
}
|
|
2131
2047
|
// src/generators.ts
|
|
2132
2048
|
/** The largest count of boxes that a generator makes. */
|
|
@@ -2347,8 +2263,10 @@ function containerOf(size, w, h, shape) {
|
|
|
2347
2263
|
aspectWeight: 0
|
|
2348
2264
|
}
|
|
2349
2265
|
}[size];
|
|
2350
|
-
|
|
2351
|
-
|
|
2266
|
+
return shape === void 0 ? container : {
|
|
2267
|
+
...container,
|
|
2268
|
+
shape
|
|
2269
|
+
};
|
|
2352
2270
|
}
|
|
2353
2271
|
/** This function gives the 4 corners of a rectangle, clockwise from the top left. */
|
|
2354
2272
|
const corners = (x, y, w, h) => [
|
|
@@ -3040,6 +2958,6 @@ function corpus(name, specs) {
|
|
|
3040
2958
|
inputs
|
|
3041
2959
|
};
|
|
3042
2960
|
}
|
|
3043
|
-
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 };
|
|
3044
2962
|
|
|
3045
2963
|
//# sourceMappingURL=index.js.map
|