@mark1russell7/packing 0.2.10 → 0.2.12
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 +13 -0
- package/build/index.d.ts +81 -9
- package/build/index.d.ts.map +1 -1
- package/build/index.js +555 -200
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/aspect-fit.ts +133 -0
- package/src/corpus.ts +2 -5
- package/src/frame.ts +24 -8
- package/src/generators.ts +58 -15
- package/src/index.ts +4 -1
- package/src/maxrects.ts +67 -17
- package/src/portfolio-strategy.ts +13 -6
- package/src/portfolio.ts +13 -30
- package/src/registry.ts +46 -3
- package/src/select-key.ts +48 -0
package/build/index.js
CHANGED
|
@@ -369,27 +369,38 @@ function readFrame(input, frame, trace, moved = [], turns) {
|
|
|
369
369
|
};
|
|
370
370
|
}
|
|
371
371
|
/**
|
|
372
|
-
* This function gives the
|
|
373
|
-
*
|
|
372
|
+
* This function gives the far edges of a packing from the corner of the container. It reads the items with their margins,
|
|
373
|
+
* the inset and the obstacles. With `turn`, a placement with `turn: 90` reads the turned item.
|
|
374
374
|
*/
|
|
375
|
-
function
|
|
375
|
+
function extentOf(input, placements, moved, origin = containerBounds(input), inset = input.container.inset ?? 0, turn) {
|
|
376
376
|
const items = new Map(input.items.map((i) => [i.id, i]));
|
|
377
377
|
let right = 0;
|
|
378
378
|
let bottom = 0;
|
|
379
379
|
for (const p of placements) {
|
|
380
380
|
const given = items.get(p.id);
|
|
381
|
-
const item = p.turn === 90 &&
|
|
381
|
+
const item = p.turn === 90 && turn !== void 0 ? turn(given) : given;
|
|
382
382
|
const m = marginOf(item);
|
|
383
|
-
right = Math.max(right, p.x + item.w + m.right +
|
|
384
|
-
bottom = Math.max(bottom, p.y + item.h + m.bottom +
|
|
383
|
+
right = Math.max(right, p.x + item.w + m.right + inset - origin.x);
|
|
384
|
+
bottom = Math.max(bottom, p.y + item.h + m.bottom + inset - origin.y);
|
|
385
385
|
}
|
|
386
386
|
const movedById = new Map(moved.map((m) => [m.id, m]));
|
|
387
387
|
for (const o of input.obstacles ?? []) {
|
|
388
388
|
const b = boundsOf(o.region);
|
|
389
389
|
const at = movedById.get(o.id) ?? b;
|
|
390
|
-
right = Math.max(right, at.x + b.w -
|
|
391
|
-
bottom = Math.max(bottom, at.y + b.h -
|
|
390
|
+
right = Math.max(right, at.x + b.w - origin.x);
|
|
391
|
+
bottom = Math.max(bottom, at.y + b.h - origin.y);
|
|
392
392
|
}
|
|
393
|
+
return {
|
|
394
|
+
right,
|
|
395
|
+
bottom
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* This function gives the size of the container after a packing. A fixed side keeps its size. A free side goes to the
|
|
400
|
+
* far edge of the items with their margins and the inset, or of the obstacles.
|
|
401
|
+
*/
|
|
402
|
+
function sizeOf(input, frame, placements, moved, turns) {
|
|
403
|
+
const { right, bottom } = extentOf(input, placements, moved, frame.bounds, frame.inset, turns?.turn);
|
|
393
404
|
return {
|
|
394
405
|
w: Number.isFinite(frame.bounds.w) ? frame.bounds.w : right,
|
|
395
406
|
h: Number.isFinite(frame.bounds.h) ? frame.bounds.h : bottom
|
|
@@ -868,14 +879,18 @@ function inside(a, b) {
|
|
|
868
879
|
function cut(free, b, minW, minH) {
|
|
869
880
|
const kept = [];
|
|
870
881
|
const parts = [];
|
|
882
|
+
const sides = [];
|
|
871
883
|
let hits = 0;
|
|
872
|
-
const add = (x0, y0, x1, y1) => {
|
|
873
|
-
if (x1 - x0 >= minW && y1 - y0 >= minH)
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
884
|
+
const add = (side, x0, y0, x1, y1) => {
|
|
885
|
+
if (x1 - x0 >= minW && y1 - y0 >= minH) {
|
|
886
|
+
parts.push({
|
|
887
|
+
x0,
|
|
888
|
+
y0,
|
|
889
|
+
x1,
|
|
890
|
+
y1
|
|
891
|
+
});
|
|
892
|
+
sides.push(side);
|
|
893
|
+
}
|
|
879
894
|
};
|
|
880
895
|
for (const f of free) {
|
|
881
896
|
if (b.x0 >= f.x1 || b.x1 <= f.x0 || b.y0 >= f.y1 || b.y1 <= f.y0) {
|
|
@@ -883,21 +898,54 @@ function cut(free, b, minW, minH) {
|
|
|
883
898
|
continue;
|
|
884
899
|
}
|
|
885
900
|
hits++;
|
|
886
|
-
if (b.x0 > f.x0) add(f.x0, f.y0, b.x0, f.y1);
|
|
887
|
-
if (b.x1 < f.x1) add(b.x1, f.y0, f.x1, f.y1);
|
|
888
|
-
if (b.y0 > f.y0) add(f.x0, f.y0, f.x1, b.y0);
|
|
889
|
-
if (b.y1 < f.y1) add(f.x0, b.y1, f.x1, f.y1);
|
|
901
|
+
if (b.x0 > f.x0) add(0, f.x0, f.y0, b.x0, f.y1);
|
|
902
|
+
if (b.x1 < f.x1) add(1, b.x1, f.y0, f.x1, f.y1);
|
|
903
|
+
if (b.y0 > f.y0) add(2, f.x0, f.y0, f.x1, b.y0);
|
|
904
|
+
if (b.y1 < f.y1) add(3, f.x0, b.y1, f.x1, f.y1);
|
|
905
|
+
}
|
|
906
|
+
if (parts.length === 0) return {
|
|
907
|
+
free: kept,
|
|
908
|
+
hits
|
|
909
|
+
};
|
|
910
|
+
const near = [
|
|
911
|
+
[],
|
|
912
|
+
[],
|
|
913
|
+
[],
|
|
914
|
+
[]
|
|
915
|
+
];
|
|
916
|
+
for (const k of kept) {
|
|
917
|
+
if (k.x1 === b.x0) near[0].push(k);
|
|
918
|
+
if (k.x0 === b.x1) near[1].push(k);
|
|
919
|
+
if (k.y1 === b.y0) near[2].push(k);
|
|
920
|
+
if (k.y0 === b.y1) near[3].push(k);
|
|
921
|
+
}
|
|
922
|
+
const order = parts.map((_, i) => i).sort((i, j) => byExtent(parts[i], parts[j]) || i - j);
|
|
923
|
+
const gone = new Uint8Array(parts.length);
|
|
924
|
+
const stay = [
|
|
925
|
+
[],
|
|
926
|
+
[],
|
|
927
|
+
[],
|
|
928
|
+
[]
|
|
929
|
+
];
|
|
930
|
+
for (const i of order) {
|
|
931
|
+
const p = parts[i];
|
|
932
|
+
const own = stay[sides[i]];
|
|
933
|
+
let drop = false;
|
|
934
|
+
for (const q of own) if (inside(p, q)) {
|
|
935
|
+
drop = true;
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
if (drop) gone[i] = 1;
|
|
939
|
+
else own.push(p);
|
|
890
940
|
}
|
|
891
|
-
const count = kept.length;
|
|
892
941
|
for (let i = 0; i < parts.length; i++) {
|
|
942
|
+
if (gone[i] === 1) continue;
|
|
893
943
|
const p = parts[i];
|
|
894
944
|
let drop = false;
|
|
895
|
-
for (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
if (inside(p, q) && (j < i || !inside(q, p))) drop = true;
|
|
945
|
+
for (const k of near[sides[i]]) if (inside(p, k)) {
|
|
946
|
+
drop = true;
|
|
947
|
+
break;
|
|
899
948
|
}
|
|
900
|
-
for (let k = 0; k < count && !drop; k++) if (inside(p, kept[k])) drop = true;
|
|
901
949
|
if (!drop) kept.push(p);
|
|
902
950
|
}
|
|
903
951
|
return {
|
|
@@ -905,6 +953,17 @@ function cut(free, b, minW, minH) {
|
|
|
905
953
|
hits
|
|
906
954
|
};
|
|
907
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* This function orders two boxes. The smaller `x0` comes first, then the smaller `y0`, then the larger `x1`, then the
|
|
958
|
+
* larger `y1`. Thus a box comes before each other box in it.
|
|
959
|
+
*/
|
|
960
|
+
function byExtent(a, b) {
|
|
961
|
+
if (a.x0 !== b.x0) return a.x0 < b.x0 ? -1 : 1;
|
|
962
|
+
if (a.y0 !== b.y0) return a.y0 < b.y0 ? -1 : 1;
|
|
963
|
+
if (a.x1 !== b.x1) return a.x1 > b.x1 ? -1 : 1;
|
|
964
|
+
if (a.y1 !== b.y1) return a.y1 > b.y1 ? -1 : 1;
|
|
965
|
+
return 0;
|
|
966
|
+
}
|
|
908
967
|
/** This function adds a range to the edges at a coordinate. */
|
|
909
968
|
function addEdge(edges, at, a, b) {
|
|
910
969
|
const list = edges.get(at);
|
|
@@ -1109,22 +1168,29 @@ function runMaxRects(rects, outer, options = {}) {
|
|
|
1109
1168
|
s1 = -contact(f.x0, f.y0, bw, bh);
|
|
1110
1169
|
s2 = f.y0 + bh;
|
|
1111
1170
|
}
|
|
1112
|
-
const
|
|
1171
|
+
const x = f.x0;
|
|
1172
|
+
const y = f.y0;
|
|
1173
|
+
if (best === void 0 || s1 < best.s1 || s1 === best.s1 && (s2 < best.s2 || s2 === best.s2 && (y < best.y || y === best.y && x < best.x))) best = {
|
|
1113
1174
|
index: i,
|
|
1114
1175
|
turn: t === 1,
|
|
1115
|
-
x
|
|
1116
|
-
y
|
|
1176
|
+
x,
|
|
1177
|
+
y,
|
|
1117
1178
|
w: bw,
|
|
1118
1179
|
h: bh,
|
|
1119
1180
|
s1,
|
|
1120
1181
|
s2
|
|
1121
1182
|
};
|
|
1122
|
-
if (
|
|
1123
|
-
const ly =
|
|
1124
|
-
if (low === void 0 || ly < low.s1 || ly === low.s1 &&
|
|
1125
|
-
|
|
1183
|
+
if (rule === "bl") continue;
|
|
1184
|
+
const ly = y + bh;
|
|
1185
|
+
if (low === void 0 || ly < low.s1 || ly === low.s1 && x < low.s2) low = {
|
|
1186
|
+
index: i,
|
|
1187
|
+
turn: t === 1,
|
|
1188
|
+
x,
|
|
1189
|
+
y,
|
|
1190
|
+
w: bw,
|
|
1191
|
+
h: bh,
|
|
1126
1192
|
s1: ly,
|
|
1127
|
-
s2:
|
|
1193
|
+
s2: x
|
|
1128
1194
|
};
|
|
1129
1195
|
}
|
|
1130
1196
|
}
|
|
@@ -3575,6 +3641,346 @@ function toRects(output, rects, outer) {
|
|
|
3575
3641
|
}
|
|
3576
3642
|
return outer;
|
|
3577
3643
|
}
|
|
3644
|
+
// src/check.ts
|
|
3645
|
+
/** This function tells if the insides of two boxes overlap by more than a tolerance on each axis. */
|
|
3646
|
+
function overlap(a, b, tolerance) {
|
|
3647
|
+
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;
|
|
3648
|
+
}
|
|
3649
|
+
/** This function gives the box made smaller by a tolerance on each side, as a rectangle. */
|
|
3650
|
+
function inner(box, tolerance) {
|
|
3651
|
+
return {
|
|
3652
|
+
x: box.l + tolerance,
|
|
3653
|
+
y: box.t + tolerance,
|
|
3654
|
+
w: box.r - box.l - 2 * tolerance,
|
|
3655
|
+
h: box.b - box.t - 2 * tolerance
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* This function counts the breaks of the 4 hard rules of the packer for an output.
|
|
3660
|
+
*
|
|
3661
|
+
* - `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`.
|
|
3662
|
+
* - `in-container`: each item with its margin that goes out of a fixed side by more than `tolerance.overlap`. The inset of
|
|
3663
|
+
* the container makes each fixed side smaller (decision 3 of `docs/PACKER.md`).
|
|
3664
|
+
* - `locked-kept`: each locked item that moved by more than `RECORD_EPS`, or that has no placement.
|
|
3665
|
+
* - `complete`: each item with no placement that is not in the overflow. A locked item breaks `locked-kept` in its place.
|
|
3666
|
+
*
|
|
3667
|
+
* A locked item with no placement stays at its input position for `no-overlap` and `in-container`. A locked item with no
|
|
3668
|
+
* `x` or no `y` in the input is a bad input. The rules skip it, as `packHard` of the contract does. `checkInput` finds it.
|
|
3669
|
+
*
|
|
3670
|
+
* The test of an obstacle is exact: a circle, an ellipse and a polygon with holes count only their insides, as in `packHard`.
|
|
3671
|
+
* The rules read a shape of the container or of an item as its bounding box (decision 5). The bounding box of an item
|
|
3672
|
+
* shape is relative to the position of the item. The result has no tolerance in it.
|
|
3673
|
+
*
|
|
3674
|
+
* With `options.exactContainer`, a shaped container with the size `fixed` also uses its true outline for `in-container`.
|
|
3675
|
+
* A box then counts when `rectInRegion` of the contract tells that it is not in the shape, as in `packHard`.
|
|
3676
|
+
* For that test, the box grows by the inset and becomes smaller by `tolerance.overlap` on each side. The default is `false`.
|
|
3677
|
+
*/
|
|
3678
|
+
function checkHard(input, output, tolerance = DEFAULT_TOLERANCE, options = {}) {
|
|
3679
|
+
const tol = tolerance.overlap;
|
|
3680
|
+
const placements = /* @__PURE__ */ new Map();
|
|
3681
|
+
for (const p of output.placements) if (!placements.has(p.id)) placements.set(p.id, p);
|
|
3682
|
+
const overflow = new Set(output.overflow);
|
|
3683
|
+
let lockedKept = 0;
|
|
3684
|
+
let complete = 0;
|
|
3685
|
+
const boxes = [];
|
|
3686
|
+
for (const item of input.items) {
|
|
3687
|
+
if (item.locked === true && (item.x === void 0 || item.y === void 0)) continue;
|
|
3688
|
+
const lockedAt = item.locked === true ? {
|
|
3689
|
+
x: item.x,
|
|
3690
|
+
y: item.y
|
|
3691
|
+
} : void 0;
|
|
3692
|
+
let p = placements.get(item.id);
|
|
3693
|
+
if (p === void 0) {
|
|
3694
|
+
if (lockedAt === void 0) {
|
|
3695
|
+
if (!overflow.has(item.id)) complete += 1;
|
|
3696
|
+
continue;
|
|
3697
|
+
}
|
|
3698
|
+
lockedKept += 1;
|
|
3699
|
+
p = lockedAt;
|
|
3700
|
+
} else if (lockedAt !== void 0) {
|
|
3701
|
+
if (Math.abs(p.x - lockedAt.x) > RECORD_EPS || Math.abs(p.y - lockedAt.y) > RECORD_EPS) lockedKept += 1;
|
|
3702
|
+
}
|
|
3703
|
+
const placed = p.turn === 90 ? turnItem(item) : item;
|
|
3704
|
+
const m = marginOf(placed);
|
|
3705
|
+
const area = placed.shape === void 0 ? {
|
|
3706
|
+
x: 0,
|
|
3707
|
+
y: 0,
|
|
3708
|
+
w: placed.w,
|
|
3709
|
+
h: placed.h
|
|
3710
|
+
} : boundsOf(placed.shape);
|
|
3711
|
+
const l = p.x + area.x;
|
|
3712
|
+
const t = p.y + area.y;
|
|
3713
|
+
boxes.push({
|
|
3714
|
+
l: l - m.left,
|
|
3715
|
+
t: t - m.top,
|
|
3716
|
+
r: l + area.w + m.right,
|
|
3717
|
+
b: t + area.h + m.bottom
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
const moved = new Map(output.moved.map((m) => [m.id, m]));
|
|
3721
|
+
const obstacles = (input.obstacles ?? []).map((o) => {
|
|
3722
|
+
const at = moved.get(o.id);
|
|
3723
|
+
if (at === void 0) return o.region;
|
|
3724
|
+
const b = boundsOf(o.region);
|
|
3725
|
+
return translate(o.region, at.x - b.x, at.y - b.y);
|
|
3726
|
+
});
|
|
3727
|
+
let overlaps = 0;
|
|
3728
|
+
for (const [i, a] of boxes.entries()) {
|
|
3729
|
+
for (let j = i + 1; j < boxes.length; j++) if (overlap(a, boxes[j], tol)) overlaps += 1;
|
|
3730
|
+
if (obstacles.length === 0) continue;
|
|
3731
|
+
const rect = inner(a, tol);
|
|
3732
|
+
for (const region of obstacles) if (regionHitsRect(region, rect)) overlaps += 1;
|
|
3733
|
+
}
|
|
3734
|
+
const { container } = input;
|
|
3735
|
+
const shape = container.shape === void 0 ? void 0 : boundsOf(container.shape);
|
|
3736
|
+
const x = shape?.x ?? 0;
|
|
3737
|
+
const y = shape?.y ?? 0;
|
|
3738
|
+
const w = shape?.w ?? container.w ?? Infinity;
|
|
3739
|
+
const h = shape?.h ?? container.h ?? Infinity;
|
|
3740
|
+
const inset = container.inset ?? 0;
|
|
3741
|
+
const fixedW = container.fixed === "size" || container.fixed === "width";
|
|
3742
|
+
const fixedH = container.fixed === "size" || container.fixed === "height";
|
|
3743
|
+
const exact = options.exactContainer === true && container.fixed === "size" ? container.shape : void 0;
|
|
3744
|
+
let outside = 0;
|
|
3745
|
+
for (const box of boxes) {
|
|
3746
|
+
const outX = fixedW && (box.l < x + inset - tol || box.r > x + w - inset + tol);
|
|
3747
|
+
const outY = fixedH && (box.t < y + inset - tol || box.b > y + h - inset + tol);
|
|
3748
|
+
if (outX || outY) outside += 1;
|
|
3749
|
+
else if (exact !== void 0 && !rectInRegion(exact, inner({
|
|
3750
|
+
l: box.l - inset,
|
|
3751
|
+
t: box.t - inset,
|
|
3752
|
+
r: box.r + inset,
|
|
3753
|
+
b: box.b + inset
|
|
3754
|
+
}, tol))) outside += 1;
|
|
3755
|
+
}
|
|
3756
|
+
return {
|
|
3757
|
+
"no-overlap": overlaps,
|
|
3758
|
+
"in-container": outside,
|
|
3759
|
+
"locked-kept": lockedKept,
|
|
3760
|
+
complete
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
// src/select-key.ts
|
|
3764
|
+
/** This function gives the count of the hard breaks of an output: the sum of `checkHard` with `exactContainer`. */
|
|
3765
|
+
function hardBreaks(input, output) {
|
|
3766
|
+
return Object.values(checkHard(input, output, void 0, { exactContainer: true })).reduce((sum, n) => sum + n, 0);
|
|
3767
|
+
}
|
|
3768
|
+
/** This function gives the level of a measure id with its spec of `MEASURE_SPECS`. It throws a `RangeError` for an unknown id. */
|
|
3769
|
+
function measureLevel(id) {
|
|
3770
|
+
const spec = MEASURE_SPECS[id];
|
|
3771
|
+
if (spec === void 0 || id === "hard") throw new RangeError(`A level of a key is a measure of MEASURE_SPECS, not "${id}".`);
|
|
3772
|
+
return {
|
|
3773
|
+
id,
|
|
3774
|
+
spec
|
|
3775
|
+
};
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* This function makes the key of a pick: `hardBreaks`, then the measures of `levels` in order, then `outputHash`.
|
|
3779
|
+
*
|
|
3780
|
+
* - The levels read the measures after `roundMeasures`, as a record keeps them. A replay of the records gives the same pick.
|
|
3781
|
+
* - The measures of one output are computed one time.
|
|
3782
|
+
* - `view` gives the input of the measures. The default is the input. The hard breaks always read the input.
|
|
3783
|
+
*/
|
|
3784
|
+
function measureKey(levels, view = (input) => input) {
|
|
3785
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
3786
|
+
const measured = (input, output) => {
|
|
3787
|
+
let m = cache.get(output);
|
|
3788
|
+
if (m === void 0) {
|
|
3789
|
+
m = roundMeasures(measure(view(input), output));
|
|
3790
|
+
cache.set(output, m);
|
|
3791
|
+
}
|
|
3792
|
+
return m;
|
|
3793
|
+
};
|
|
3794
|
+
return {
|
|
3795
|
+
hard: hardBreaks,
|
|
3796
|
+
levels: levels.map((level) => ({
|
|
3797
|
+
id: level.id,
|
|
3798
|
+
spec: level.spec,
|
|
3799
|
+
value: (input, output) => measured(input, output)[level.id]
|
|
3800
|
+
})),
|
|
3801
|
+
hash: outputHash
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
// src/aspect-fit.ts
|
|
3805
|
+
/** The id of the trace stage of `aspectFit`. Its measure `move.aspectPick` is 1 when the pick is not the output of the wrapped strategy. */
|
|
3806
|
+
const ASPECT_FIT_STAGE = "aspect-fit";
|
|
3807
|
+
/**
|
|
3808
|
+
* The factors of the widths of `aspectFit`. Each width is a factor times the square root of the area of the first output
|
|
3809
|
+
* times the target aspect. The steps are approximately 5% of the width, thus approximately 0.1 of the aspect error.
|
|
3810
|
+
*/
|
|
3811
|
+
const ASPECT_FIT_FACTORS = Object.freeze([
|
|
3812
|
+
.86,
|
|
3813
|
+
.9,
|
|
3814
|
+
.95,
|
|
3815
|
+
1,
|
|
3816
|
+
1.05,
|
|
3817
|
+
1.1,
|
|
3818
|
+
1.16
|
|
3819
|
+
]);
|
|
3820
|
+
/**
|
|
3821
|
+
* The levels of the pick of `aspectFit`, in order. Level 0 is `hard`.
|
|
3822
|
+
*
|
|
3823
|
+
* - `hard`: the count of hard breaks of `checkHard` with `exactContainer`. Lower is better, with the minimum effect 0.
|
|
3824
|
+
* - `pack.overflow.excessArea`: the overflow area above its bound. Lower is better.
|
|
3825
|
+
* - `pack.aspectError`: the distance from the target aspect. Lower is better, with the minimum effect 0.01.
|
|
3826
|
+
* - `pack.area`: the area of the hull. Lower is better, with the minimum effect of 1% of the area.
|
|
3827
|
+
*/
|
|
3828
|
+
const ASPECT_FIT_LEVELS = Object.freeze([
|
|
3829
|
+
{
|
|
3830
|
+
id: "hard",
|
|
3831
|
+
spec: {
|
|
3832
|
+
direction: "lower",
|
|
3833
|
+
minEffect: 0
|
|
3834
|
+
}
|
|
3835
|
+
},
|
|
3836
|
+
{
|
|
3837
|
+
id: "pack.overflow.excessArea",
|
|
3838
|
+
spec: MEASURE_SPECS["pack.overflow.excessArea"]
|
|
3839
|
+
},
|
|
3840
|
+
{
|
|
3841
|
+
id: "pack.aspectError",
|
|
3842
|
+
spec: MEASURE_SPECS["pack.aspectError"]
|
|
3843
|
+
},
|
|
3844
|
+
{
|
|
3845
|
+
id: "pack.area",
|
|
3846
|
+
spec: MEASURE_SPECS["pack.area"]
|
|
3847
|
+
}
|
|
3848
|
+
]);
|
|
3849
|
+
/** This function gives a shape with the origin of `shape` and the width `w`. A shape that is not a rectangle keeps the holes of its polygon. */
|
|
3850
|
+
function stripOf(shape, w) {
|
|
3851
|
+
const b = boundsOf$1(shape);
|
|
3852
|
+
if (shape.kind === "rect") return {
|
|
3853
|
+
kind: "rect",
|
|
3854
|
+
x: b.x,
|
|
3855
|
+
y: b.y,
|
|
3856
|
+
w,
|
|
3857
|
+
h: b.h
|
|
3858
|
+
};
|
|
3859
|
+
const points = [
|
|
3860
|
+
[b.x, b.y],
|
|
3861
|
+
[b.x + w, b.y],
|
|
3862
|
+
[b.x + w, b.y + b.h],
|
|
3863
|
+
[b.x, b.y + b.h]
|
|
3864
|
+
];
|
|
3865
|
+
return shape.kind === "polygon" && shape.holes !== void 0 ? {
|
|
3866
|
+
kind: "polygon",
|
|
3867
|
+
points,
|
|
3868
|
+
holes: shape.holes
|
|
3869
|
+
} : {
|
|
3870
|
+
kind: "polygon",
|
|
3871
|
+
points
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3874
|
+
/** This function gives an input with the fixed width `w` and a free height. A shape becomes a strip of the width `w`. */
|
|
3875
|
+
function atWidth(input, w) {
|
|
3876
|
+
const c = input.container;
|
|
3877
|
+
return {
|
|
3878
|
+
...input,
|
|
3879
|
+
container: {
|
|
3880
|
+
...c,
|
|
3881
|
+
fixed: "width",
|
|
3882
|
+
w,
|
|
3883
|
+
...c.shape === void 0 ? {} : { shape: stripOf(c.shape, w) }
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
/** This function gives the widths of `aspectFit` for an input and the first output. Each width is narrower than a fixed width. */
|
|
3888
|
+
function widthsOf(input, first, target) {
|
|
3889
|
+
const area = measure(input, first)["pack.area"];
|
|
3890
|
+
if (!(area > 0)) return [];
|
|
3891
|
+
const inset = input.container.inset ?? 0;
|
|
3892
|
+
const fixed = input.container.fixed === "width" ? containerBounds(input).w : Infinity;
|
|
3893
|
+
let widest = 0;
|
|
3894
|
+
for (const item of input.items) {
|
|
3895
|
+
const m = marginOf(item);
|
|
3896
|
+
widest = Math.max(widest, item.w + m.left + m.right);
|
|
3897
|
+
}
|
|
3898
|
+
const ideal = Math.sqrt(area * target);
|
|
3899
|
+
const out = [];
|
|
3900
|
+
for (const f of ASPECT_FIT_FACTORS) {
|
|
3901
|
+
const w = Math.max(Math.round(f * ideal + 2 * inset), Math.ceil(widest + 2 * inset));
|
|
3902
|
+
if (w < fixed && !out.includes(w)) out.push(w);
|
|
3903
|
+
}
|
|
3904
|
+
return out;
|
|
3905
|
+
}
|
|
3906
|
+
/** This function packs an input with a free height or a free size near the target aspect. */
|
|
3907
|
+
function fit(input, target, inner, options, context) {
|
|
3908
|
+
const fresh = () => createContext(context.seed, {
|
|
3909
|
+
...context.budget === void 0 ? {} : { budget: context.budget },
|
|
3910
|
+
...context.previous === void 0 ? {} : { previous: context.previous }
|
|
3911
|
+
});
|
|
3912
|
+
const first = inner.pack(input, options, fresh());
|
|
3913
|
+
const widths = widthsOf(input, first, target);
|
|
3914
|
+
if (widths.length === 0) return first;
|
|
3915
|
+
const origin = containerBounds(input);
|
|
3916
|
+
const inset = input.container.inset ?? 0;
|
|
3917
|
+
const free = input.container.fixed === "none";
|
|
3918
|
+
const sized = (out) => {
|
|
3919
|
+
const w = free ? extentOf(input, out.placements, out.moved, origin, inset, turnItem).right : origin.w;
|
|
3920
|
+
return {
|
|
3921
|
+
...out,
|
|
3922
|
+
size: {
|
|
3923
|
+
w,
|
|
3924
|
+
h: out.size.h
|
|
3925
|
+
}
|
|
3926
|
+
};
|
|
3927
|
+
};
|
|
3928
|
+
const members = [{
|
|
3929
|
+
id: inner.id,
|
|
3930
|
+
run: () => first
|
|
3931
|
+
}, ...widths.map((w) => ({
|
|
3932
|
+
id: `${inner.id}@${w}`,
|
|
3933
|
+
run: (x) => sized(inner.pack(atWidth(x, w), options, fresh()))
|
|
3934
|
+
}))];
|
|
3935
|
+
const view = (x) => x.container.aspect === target ? x : {
|
|
3936
|
+
...x,
|
|
3937
|
+
container: {
|
|
3938
|
+
...x.container,
|
|
3939
|
+
aspect: target
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
const got = portfolio(ASPECT_FIT_STAGE, members, measureKey(ASPECT_FIT_LEVELS.slice(1), view)).pick(input);
|
|
3943
|
+
const stage = {
|
|
3944
|
+
id: ASPECT_FIT_STAGE,
|
|
3945
|
+
measures: { "move.aspectPick": pickMoves(got.trace).pick }
|
|
3946
|
+
};
|
|
3947
|
+
return {
|
|
3948
|
+
...got.output,
|
|
3949
|
+
trace: [...got.output.trace ?? [], stage]
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3952
|
+
/**
|
|
3953
|
+
* This function wraps a strategy. For a free-size container, it packs at some widths near the target aspect and keeps the best output.
|
|
3954
|
+
*
|
|
3955
|
+
* - The target is `targetAspect`, else `aspect` of the container, else 1.
|
|
3956
|
+
* - A container with the size `fixed` goes to the wrapped strategy without a change.
|
|
3957
|
+
* - The first member is the output of the wrapped strategy. Each other member packs at one fixed width of `ASPECT_FIT_FACTORS`.
|
|
3958
|
+
* - The key is `ASPECT_FIT_LEVELS`, then `outputHash`. The aspect error reads the target. `selectBest` of the contract picks the winner.
|
|
3959
|
+
* - A fixed height goes through the turn of the axes, with the target `1 / target`.
|
|
3960
|
+
* - The function throws a `RangeError` for a target that is not a number above 0.
|
|
3961
|
+
*/
|
|
3962
|
+
function aspectFit(inner, options = {}) {
|
|
3963
|
+
const given = options.targetAspect;
|
|
3964
|
+
if (given !== void 0 && !(Number.isFinite(given) && given > 0)) throw new RangeError(`The target aspect of aspectFit is a number above 0, not ${String(given)}.`);
|
|
3965
|
+
return {
|
|
3966
|
+
id: `aspect-fit(${inner.id})`,
|
|
3967
|
+
family: inner.family,
|
|
3968
|
+
catalog: [],
|
|
3969
|
+
supports: inner.supports,
|
|
3970
|
+
defaults: inner.defaults,
|
|
3971
|
+
deterministic: inner.deterministic,
|
|
3972
|
+
pack(input, o, context) {
|
|
3973
|
+
const c = input.container;
|
|
3974
|
+
if (c.fixed === "size") return inner.pack(input, o, context);
|
|
3975
|
+
const target = given ?? c.aspect ?? 1;
|
|
3976
|
+
if (c.fixed === "height") return viaAxes(input, context.previous, (turned, previous) => fit(turned, 1 / target, inner, o, {
|
|
3977
|
+
...context,
|
|
3978
|
+
...previous === void 0 ? {} : { previous }
|
|
3979
|
+
}));
|
|
3980
|
+
return fit(input, target, inner, o, context);
|
|
3981
|
+
}
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3578
3984
|
// src/portfolio-sets.ts
|
|
3579
3985
|
/** The table of the per-cell portfolio: the sets of 1, 2 and 3 members of each cell, with their training scores. The run 40652567756 gives it. */
|
|
3580
3986
|
const PORTFOLIO_SETS = {
|
|
@@ -5796,125 +6202,6 @@ function portfolioDefaultMembers(input, k, table = PORTFOLIO_SETS) {
|
|
|
5796
6202
|
const count = portfolioSetOf(input, table).sets.defaultMembers ?? 3;
|
|
5797
6203
|
return k === void 0 ? count : Math.min(count, k);
|
|
5798
6204
|
}
|
|
5799
|
-
// src/check.ts
|
|
5800
|
-
/** This function tells if the insides of two boxes overlap by more than a tolerance on each axis. */
|
|
5801
|
-
function overlap(a, b, tolerance) {
|
|
5802
|
-
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;
|
|
5803
|
-
}
|
|
5804
|
-
/** This function gives the box made smaller by a tolerance on each side, as a rectangle. */
|
|
5805
|
-
function inner(box, tolerance) {
|
|
5806
|
-
return {
|
|
5807
|
-
x: box.l + tolerance,
|
|
5808
|
-
y: box.t + tolerance,
|
|
5809
|
-
w: box.r - box.l - 2 * tolerance,
|
|
5810
|
-
h: box.b - box.t - 2 * tolerance
|
|
5811
|
-
};
|
|
5812
|
-
}
|
|
5813
|
-
/**
|
|
5814
|
-
* This function counts the breaks of the 4 hard rules of the packer for an output.
|
|
5815
|
-
*
|
|
5816
|
-
* - `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`.
|
|
5817
|
-
* - `in-container`: each item with its margin that goes out of a fixed side by more than `tolerance.overlap`. The inset of
|
|
5818
|
-
* the container makes each fixed side smaller (decision 3 of `docs/PACKER.md`).
|
|
5819
|
-
* - `locked-kept`: each locked item that moved by more than `RECORD_EPS`, or that has no placement.
|
|
5820
|
-
* - `complete`: each item with no placement that is not in the overflow. A locked item breaks `locked-kept` in its place.
|
|
5821
|
-
*
|
|
5822
|
-
* A locked item with no placement stays at its input position for `no-overlap` and `in-container`. A locked item with no
|
|
5823
|
-
* `x` or no `y` in the input is a bad input. The rules skip it, as `packHard` of the contract does. `checkInput` finds it.
|
|
5824
|
-
*
|
|
5825
|
-
* The test of an obstacle is exact: a circle, an ellipse and a polygon with holes count only their insides, as in `packHard`.
|
|
5826
|
-
* The rules read a shape of the container or of an item as its bounding box (decision 5). The bounding box of an item
|
|
5827
|
-
* shape is relative to the position of the item. The result has no tolerance in it.
|
|
5828
|
-
*
|
|
5829
|
-
* With `options.exactContainer`, a shaped container with the size `fixed` also uses its true outline for `in-container`.
|
|
5830
|
-
* A box then counts when `rectInRegion` of the contract tells that it is not in the shape, as in `packHard`.
|
|
5831
|
-
* For that test, the box grows by the inset and becomes smaller by `tolerance.overlap` on each side. The default is `false`.
|
|
5832
|
-
*/
|
|
5833
|
-
function checkHard(input, output, tolerance = DEFAULT_TOLERANCE, options = {}) {
|
|
5834
|
-
const tol = tolerance.overlap;
|
|
5835
|
-
const placements = /* @__PURE__ */ new Map();
|
|
5836
|
-
for (const p of output.placements) if (!placements.has(p.id)) placements.set(p.id, p);
|
|
5837
|
-
const overflow = new Set(output.overflow);
|
|
5838
|
-
let lockedKept = 0;
|
|
5839
|
-
let complete = 0;
|
|
5840
|
-
const boxes = [];
|
|
5841
|
-
for (const item of input.items) {
|
|
5842
|
-
if (item.locked === true && (item.x === void 0 || item.y === void 0)) continue;
|
|
5843
|
-
const lockedAt = item.locked === true ? {
|
|
5844
|
-
x: item.x,
|
|
5845
|
-
y: item.y
|
|
5846
|
-
} : void 0;
|
|
5847
|
-
let p = placements.get(item.id);
|
|
5848
|
-
if (p === void 0) {
|
|
5849
|
-
if (lockedAt === void 0) {
|
|
5850
|
-
if (!overflow.has(item.id)) complete += 1;
|
|
5851
|
-
continue;
|
|
5852
|
-
}
|
|
5853
|
-
lockedKept += 1;
|
|
5854
|
-
p = lockedAt;
|
|
5855
|
-
} else if (lockedAt !== void 0) {
|
|
5856
|
-
if (Math.abs(p.x - lockedAt.x) > RECORD_EPS || Math.abs(p.y - lockedAt.y) > RECORD_EPS) lockedKept += 1;
|
|
5857
|
-
}
|
|
5858
|
-
const placed = p.turn === 90 ? turnItem(item) : item;
|
|
5859
|
-
const m = marginOf(placed);
|
|
5860
|
-
const area = placed.shape === void 0 ? {
|
|
5861
|
-
x: 0,
|
|
5862
|
-
y: 0,
|
|
5863
|
-
w: placed.w,
|
|
5864
|
-
h: placed.h
|
|
5865
|
-
} : boundsOf(placed.shape);
|
|
5866
|
-
const l = p.x + area.x;
|
|
5867
|
-
const t = p.y + area.y;
|
|
5868
|
-
boxes.push({
|
|
5869
|
-
l: l - m.left,
|
|
5870
|
-
t: t - m.top,
|
|
5871
|
-
r: l + area.w + m.right,
|
|
5872
|
-
b: t + area.h + m.bottom
|
|
5873
|
-
});
|
|
5874
|
-
}
|
|
5875
|
-
const moved = new Map(output.moved.map((m) => [m.id, m]));
|
|
5876
|
-
const obstacles = (input.obstacles ?? []).map((o) => {
|
|
5877
|
-
const at = moved.get(o.id);
|
|
5878
|
-
if (at === void 0) return o.region;
|
|
5879
|
-
const b = boundsOf(o.region);
|
|
5880
|
-
return translate(o.region, at.x - b.x, at.y - b.y);
|
|
5881
|
-
});
|
|
5882
|
-
let overlaps = 0;
|
|
5883
|
-
for (const [i, a] of boxes.entries()) {
|
|
5884
|
-
for (let j = i + 1; j < boxes.length; j++) if (overlap(a, boxes[j], tol)) overlaps += 1;
|
|
5885
|
-
if (obstacles.length === 0) continue;
|
|
5886
|
-
const rect = inner(a, tol);
|
|
5887
|
-
for (const region of obstacles) if (regionHitsRect(region, rect)) overlaps += 1;
|
|
5888
|
-
}
|
|
5889
|
-
const { container } = input;
|
|
5890
|
-
const shape = container.shape === void 0 ? void 0 : boundsOf(container.shape);
|
|
5891
|
-
const x = shape?.x ?? 0;
|
|
5892
|
-
const y = shape?.y ?? 0;
|
|
5893
|
-
const w = shape?.w ?? container.w ?? Infinity;
|
|
5894
|
-
const h = shape?.h ?? container.h ?? Infinity;
|
|
5895
|
-
const inset = container.inset ?? 0;
|
|
5896
|
-
const fixedW = container.fixed === "size" || container.fixed === "width";
|
|
5897
|
-
const fixedH = container.fixed === "size" || container.fixed === "height";
|
|
5898
|
-
const exact = options.exactContainer === true && container.fixed === "size" ? container.shape : void 0;
|
|
5899
|
-
let outside = 0;
|
|
5900
|
-
for (const box of boxes) {
|
|
5901
|
-
const outX = fixedW && (box.l < x + inset - tol || box.r > x + w - inset + tol);
|
|
5902
|
-
const outY = fixedH && (box.t < y + inset - tol || box.b > y + h - inset + tol);
|
|
5903
|
-
if (outX || outY) outside += 1;
|
|
5904
|
-
else if (exact !== void 0 && !rectInRegion(exact, inner({
|
|
5905
|
-
l: box.l - inset,
|
|
5906
|
-
t: box.t - inset,
|
|
5907
|
-
r: box.r + inset,
|
|
5908
|
-
b: box.b + inset
|
|
5909
|
-
}, tol))) outside += 1;
|
|
5910
|
-
}
|
|
5911
|
-
return {
|
|
5912
|
-
"no-overlap": overlaps,
|
|
5913
|
-
"in-container": outside,
|
|
5914
|
-
"locked-kept": lockedKept,
|
|
5915
|
-
complete
|
|
5916
|
-
};
|
|
5917
|
-
}
|
|
5918
6205
|
// src/portfolio.ts
|
|
5919
6206
|
/** The id of the trace stage of `packPortfolio`. Its measure `move.pick` is 1 when the pick is not member 0. */
|
|
5920
6207
|
const PORTFOLIO_STAGE = "portfolio";
|
|
@@ -5942,10 +6229,6 @@ const PORTFOLIO_LEVELS = Object.freeze([
|
|
|
5942
6229
|
spec: MEASURE_SPECS["pack.fill"]
|
|
5943
6230
|
}
|
|
5944
6231
|
]);
|
|
5945
|
-
/** This function gives the count of the hard breaks of an output: the sum of `checkHard` with `exactContainer`. */
|
|
5946
|
-
function hardBreaks(input, output) {
|
|
5947
|
-
return Object.values(checkHard(input, output, void 0, { exactContainer: true })).reduce((sum, n) => sum + n, 0);
|
|
5948
|
-
}
|
|
5949
6232
|
/**
|
|
5950
6233
|
* This function makes an opt-in portfolio of strategies of the registry with `portfolio` of the contract.
|
|
5951
6234
|
* Its `pick(input)` packs the input with each member and gives the output of the winner, its id and the trace.
|
|
@@ -5953,10 +6236,11 @@ function hardBreaks(input, output) {
|
|
|
5953
6236
|
* - A member is a strategy id, or a `PortfolioEntry` with options. The members run in the given order.
|
|
5954
6237
|
* - `members` is a list, or a function of the input that gives the list. The strategy `portfolio` uses the function form.
|
|
5955
6238
|
* - The key is the hard breaks, then `pack.overflow.excessArea`, then `pack.fill`. `PORTFOLIO_LEVELS` gives the specs.
|
|
6239
|
+
* The option `key` gives other measures after the hard breaks.
|
|
5956
6240
|
* - The key reads the measures after `roundMeasures`, as a record keeps them. A replay of the records gives the same pick.
|
|
5957
6241
|
* - The hash of the key is `outputHash`. `selectBest` of the contract picks the winner.
|
|
5958
6242
|
* - The output of the winner gets the trace stage `portfolio` with `move.pick` of `pickMoves`.
|
|
5959
|
-
* - The function throws a `RangeError` for an unknown strategy, an empty list
|
|
6243
|
+
* - The function throws a `RangeError` for an unknown strategy, an empty list, two members with the same id and an unknown measure of `key`.
|
|
5960
6244
|
* With the function form, `pick` throws this error for the list of an input.
|
|
5961
6245
|
*/
|
|
5962
6246
|
function packPortfolio(members, options = {}) {
|
|
@@ -5973,24 +6257,7 @@ function packPortfolio(members, options = {}) {
|
|
|
5973
6257
|
};
|
|
5974
6258
|
});
|
|
5975
6259
|
const list = typeof members === "function" ? (input) => toMembers(members(input)) : toMembers(members);
|
|
5976
|
-
const
|
|
5977
|
-
const measured = (input, output) => {
|
|
5978
|
-
let m = cache.get(output);
|
|
5979
|
-
if (m === void 0) {
|
|
5980
|
-
m = roundMeasures(measure(input, output));
|
|
5981
|
-
cache.set(output, m);
|
|
5982
|
-
}
|
|
5983
|
-
return m;
|
|
5984
|
-
};
|
|
5985
|
-
const key = {
|
|
5986
|
-
hard: hardBreaks,
|
|
5987
|
-
levels: PORTFOLIO_LEVELS.slice(1).map((level) => ({
|
|
5988
|
-
id: level.id,
|
|
5989
|
-
spec: level.spec,
|
|
5990
|
-
value: (input, output) => measured(input, output)[level.id]
|
|
5991
|
-
})),
|
|
5992
|
-
hash: outputHash
|
|
5993
|
-
};
|
|
6260
|
+
const key = measureKey(options.key === void 0 ? PORTFOLIO_LEVELS.slice(1) : options.key.map(measureLevel));
|
|
5994
6261
|
const limits = {
|
|
5995
6262
|
...options.budgetMs !== void 0 ? { budgetMs: options.budgetMs } : {},
|
|
5996
6263
|
...options.maxMembers !== void 0 ? { maxMembers: options.maxMembers } : {},
|
|
@@ -6044,7 +6311,8 @@ function unionSupports() {
|
|
|
6044
6311
|
* The default output thus does not depend on the clock.
|
|
6045
6312
|
* - A member that does not support the cell of an input throws, and it does not win. When each member throws, the strategy throws the first error.
|
|
6046
6313
|
* - The option `budgetMs` goes to `portfolio` of the contract. The first member always runs.
|
|
6047
|
-
* -
|
|
6314
|
+
* - With `members`, each input gets these members, and `maxMembers` has no default. The id of the portfolio stays `portfolio`.
|
|
6315
|
+
* - The strategy throws a `RangeError` for a `k` other than 2 or 3, and for `k` with `members`.
|
|
6048
6316
|
*/
|
|
6049
6317
|
const portfolioStrategy = {
|
|
6050
6318
|
id: "portfolio",
|
|
@@ -6054,15 +6322,17 @@ const portfolioStrategy = {
|
|
|
6054
6322
|
defaults: {},
|
|
6055
6323
|
deterministic: true,
|
|
6056
6324
|
pack(input, options, context) {
|
|
6057
|
-
const { k, maxMembers, budgetMs } = options;
|
|
6325
|
+
const { k, maxMembers, budgetMs, members, key } = options;
|
|
6058
6326
|
if (k !== void 0 && k !== 2 && k !== 3) throw new RangeError(`The option k of the strategy portfolio is 2 or 3, not ${String(k)}.`);
|
|
6327
|
+
if (k !== void 0 && members !== void 0) throw new RangeError("The strategy portfolio takes the option k or the option members, not the two.");
|
|
6059
6328
|
const size = k ?? 3;
|
|
6060
|
-
const count = maxMembers ?? (k === void 0 ? portfolioDefaultMembers(input) : void 0);
|
|
6329
|
+
const count = maxMembers ?? (k === void 0 && members === void 0 ? portfolioDefaultMembers(input) : void 0);
|
|
6061
6330
|
const limits = {
|
|
6062
6331
|
...count === void 0 ? {} : { maxMembers: count },
|
|
6063
|
-
...budgetMs === void 0 ? {} : { budgetMs }
|
|
6332
|
+
...budgetMs === void 0 ? {} : { budgetMs },
|
|
6333
|
+
...key === void 0 ? {} : { key }
|
|
6064
6334
|
};
|
|
6065
|
-
return packPortfolio((x) => portfolioMembers(x, size), {
|
|
6335
|
+
return packPortfolio(members ?? ((x) => portfolioMembers(x, size)), {
|
|
6066
6336
|
id: k === void 0 ? "portfolio" : `portfolio-k${k}`,
|
|
6067
6337
|
seed: context.seed,
|
|
6068
6338
|
...limits
|
|
@@ -6078,11 +6348,42 @@ const strategies = Object.freeze([
|
|
|
6078
6348
|
push,
|
|
6079
6349
|
maxrects
|
|
6080
6350
|
]);
|
|
6351
|
+
/** This function gives the union of the supports of some strategies. */
|
|
6352
|
+
function unionOf(list) {
|
|
6353
|
+
const out = {};
|
|
6354
|
+
for (const s of list) for (const [dim, values] of Object.entries(s.supports)) {
|
|
6355
|
+
const own = out[dim] ??= [];
|
|
6356
|
+
for (const v of values) if (!own.includes(v)) own.push(v);
|
|
6357
|
+
}
|
|
6358
|
+
return out;
|
|
6359
|
+
}
|
|
6360
|
+
/**
|
|
6361
|
+
* The strategy `aspect-fit`: `aspectFit` of roadmap item k62 around the strategy of the option `strategy`. Its supports are
|
|
6362
|
+
* the union of the supports of the single strategies. A cell that the wrapped strategy does not support throws its error.
|
|
6363
|
+
* The strategy throws a `RangeError` without the option `strategy`.
|
|
6364
|
+
*/
|
|
6365
|
+
const aspectFitStrategy = {
|
|
6366
|
+
id: "aspect-fit",
|
|
6367
|
+
family: "aspect-fit",
|
|
6368
|
+
catalog: [],
|
|
6369
|
+
supports: unionOf(strategies),
|
|
6370
|
+
defaults: {},
|
|
6371
|
+
deterministic: true,
|
|
6372
|
+
pack(input, options, context) {
|
|
6373
|
+
if (options.strategy === void 0) throw new RangeError("The strategy aspect-fit needs the option strategy.");
|
|
6374
|
+
const inner = strategy(options.strategy);
|
|
6375
|
+
const merged = {
|
|
6376
|
+
...inner.defaults,
|
|
6377
|
+
...options.options
|
|
6378
|
+
};
|
|
6379
|
+
return aspectFit(inner, options.targetAspect === void 0 ? {} : { targetAspect: options.targetAspect }).pack(input, merged, context);
|
|
6380
|
+
}
|
|
6381
|
+
};
|
|
6081
6382
|
/**
|
|
6082
6383
|
* The composite strategies of the registry: each one packs an input with other strategies. They are not in `strategies`.
|
|
6083
|
-
* Since 0.2.7, the list has `portfolio` of roadmap item k20.
|
|
6384
|
+
* Since 0.2.7, the list has `portfolio` of roadmap item k20. Roadmap item k62 adds `aspect-fit`.
|
|
6084
6385
|
*/
|
|
6085
|
-
const composites = Object.freeze([portfolioStrategy]);
|
|
6386
|
+
const composites = Object.freeze([portfolioStrategy, aspectFitStrategy]);
|
|
6086
6387
|
/** This function gives the strategy with an id, also a composite strategy. It throws a `RangeError` for an id that the registry does not have. */
|
|
6087
6388
|
function strategy(id) {
|
|
6088
6389
|
const found = strategies.find((s) => s.id === id) ?? composites.find((s) => s.id === id);
|
|
@@ -6669,19 +6970,67 @@ const rectGrid = defineGenerator({
|
|
|
6669
6970
|
},
|
|
6670
6971
|
family: (p) => `rect-grid.${p.containerSize}.${p.containerShape}.${p.obstacles}.${p.sizes}.n${p.count}`,
|
|
6671
6972
|
cell: (p) => cellWith(p.containerSize, p.containerShape, p.obstacles, p.sizes, p.count),
|
|
6973
|
+
build: gridOf
|
|
6974
|
+
});
|
|
6975
|
+
/** This function builds an input of `rect-grid`: the container, then the obstacles, then the boxes. */
|
|
6976
|
+
function gridOf(seed, p) {
|
|
6977
|
+
let r = stepRandom(seed, "container");
|
|
6978
|
+
const w = int(r, 600, 1e3);
|
|
6979
|
+
const h = int(r, 400, 800);
|
|
6980
|
+
const { region, frame } = shapeOf(r, p.containerShape, w, h);
|
|
6981
|
+
const container = containerOf(p.containerSize, w, h, region);
|
|
6982
|
+
r = stepRandom(seed, "obstacles");
|
|
6983
|
+
const obstacles = obstaclesOf(r, p.obstacles, frame);
|
|
6984
|
+
r = stepRandom(seed, "boxes");
|
|
6985
|
+
return {
|
|
6986
|
+
container,
|
|
6987
|
+
obstacles,
|
|
6988
|
+
boxes: boxesOf(r, p.sizes, p.count, frame)
|
|
6989
|
+
};
|
|
6990
|
+
}
|
|
6991
|
+
/**
|
|
6992
|
+
* The generator `rect-grid`, version 2: the inputs of version 1 with the parameter `order`. With `soft`, each box gets a
|
|
6993
|
+
* rank, a random permutation of the box indexes. With `free`, the boxes have no rank.
|
|
6994
|
+
*/
|
|
6995
|
+
const rectGridV2 = defineGenerator({
|
|
6996
|
+
name: "rect-grid",
|
|
6997
|
+
version: 2,
|
|
6998
|
+
set: "corpus-v1",
|
|
6999
|
+
defaults: {
|
|
7000
|
+
containerSize: "fixed",
|
|
7001
|
+
containerShape: "rect",
|
|
7002
|
+
obstacles: "none",
|
|
7003
|
+
sizes: "uniform",
|
|
7004
|
+
order: "free",
|
|
7005
|
+
count: 100
|
|
7006
|
+
},
|
|
7007
|
+
values: {
|
|
7008
|
+
containerSize: CONTAINER_SIZES,
|
|
7009
|
+
containerShape: CONTAINER_SHAPES,
|
|
7010
|
+
obstacles: OBSTACLE_LAYOUTS,
|
|
7011
|
+
sizes: SIZE_DISTRIBUTIONS,
|
|
7012
|
+
order: ["free", "soft"]
|
|
7013
|
+
},
|
|
7014
|
+
family: (p) => `rect-grid.${p.containerSize}.${p.containerShape}.${p.obstacles}.${p.sizes}.${p.order}.n${p.count}`,
|
|
7015
|
+
cell: (p) => ({
|
|
7016
|
+
...cellWith(p.containerSize, p.containerShape, p.obstacles, p.sizes, p.count),
|
|
7017
|
+
order: p.order
|
|
7018
|
+
}),
|
|
6672
7019
|
build(seed, p) {
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
const
|
|
6676
|
-
const
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
7020
|
+
const built = gridOf(seed, p);
|
|
7021
|
+
if (p.order === "free") return built;
|
|
7022
|
+
const r = stepRandom(seed, "order");
|
|
7023
|
+
const ranks = built.boxes.map((_, i) => i);
|
|
7024
|
+
for (let i = ranks.length - 1; i > 0; i--) {
|
|
7025
|
+
const j = Math.floor(r() * (i + 1));
|
|
7026
|
+
[ranks[i], ranks[j]] = [ranks[j], ranks[i]];
|
|
7027
|
+
}
|
|
6681
7028
|
return {
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
7029
|
+
...built,
|
|
7030
|
+
boxes: built.boxes.map((b, i) => ({
|
|
7031
|
+
...b,
|
|
7032
|
+
rank: ranks[i]
|
|
7033
|
+
}))
|
|
6685
7034
|
};
|
|
6686
7035
|
}
|
|
6687
7036
|
});
|
|
@@ -6961,16 +7310,23 @@ const edgeCases = defineGenerator({
|
|
|
6961
7310
|
const generators = Object.freeze([
|
|
6962
7311
|
rectScattered,
|
|
6963
7312
|
rectGrid,
|
|
7313
|
+
rectGridV2,
|
|
6964
7314
|
gallery,
|
|
6965
7315
|
dashboard,
|
|
6966
7316
|
cards,
|
|
6967
7317
|
wordCloud,
|
|
6968
7318
|
edgeCases
|
|
6969
7319
|
]);
|
|
6970
|
-
/**
|
|
6971
|
-
function generator
|
|
6972
|
-
|
|
6973
|
-
|
|
7320
|
+
/**
|
|
7321
|
+
* This function gives the generator with a name and a version. Without a version, it gives the first generator of the name
|
|
7322
|
+
* in `generators`: version 1 of `rect-grid`. It throws a `RangeError` for a name or a version that the package does not have.
|
|
7323
|
+
*/
|
|
7324
|
+
function generator(name, version) {
|
|
7325
|
+
const named = generators.filter((g) => g.name === name);
|
|
7326
|
+
if (named.length === 0) throw new RangeError(`The package has no generator "${name}".`);
|
|
7327
|
+
if (version === void 0) return named[0];
|
|
7328
|
+
const found = named.find((g) => g.version === version);
|
|
7329
|
+
if (found === void 0) throw new RangeError(`The generator "${name}" has the versions ${named.map((g) => g.version).join(", ")}, not ${version}.`);
|
|
6974
7330
|
return found;
|
|
6975
7331
|
}
|
|
6976
7332
|
// src/corpus.ts
|
|
@@ -6988,8 +7344,7 @@ function corpus(name, specs) {
|
|
|
6988
7344
|
const inputs = [];
|
|
6989
7345
|
const ids = /* @__PURE__ */ new Set();
|
|
6990
7346
|
for (const spec of specs) {
|
|
6991
|
-
const gen = generator(spec.generator);
|
|
6992
|
-
if (spec.version !== void 0 && spec.version !== gen.version) throw new RangeError(`The generator "${gen.name}" has the version ${gen.version}, not ${spec.version}.`);
|
|
7347
|
+
const gen = generator(spec.generator, spec.version);
|
|
6993
7348
|
const params = gen.withDefaults(spec.params);
|
|
6994
7349
|
const cell = gen.cell(params);
|
|
6995
7350
|
manifest.specs.push({
|
|
@@ -7015,6 +7370,6 @@ function corpus(name, specs) {
|
|
|
7015
7370
|
inputs
|
|
7016
7371
|
};
|
|
7017
7372
|
}
|
|
7018
|
-
export { DEFAULT_TOLERANCE, EPS, MAXRECTS_BANDS, MAXRECTS_RULES, MEASURE_SPECS, PORTFOLIO_FRAME_MS, PORTFOLIO_LEVELS, PORTFOLIO_SETS, PORTFOLIO_STAGE, RECORD_EPS, Rect, SHELF_FITS, SORT_ORDERS, SPREAD_THRESHOLDS, THIN_INPUTS, UnsupportedCell, Vector, aspectError, assertSupported, canonicalJson, cards, cellKey, cellOf, checkHard, checkInput, compareRects, composites, corpus, countCell, createContext, dashboard, edgeCases, fnv1a32, freeSpace, fromRects, gallery, generator, generators, hardBreaks, hashInput, isDeterministic, logSweep, lowestWindow, marginOf, maxrects, measure, measureChange, medianRatio, mulberry32, outputHash, pack, packFree, packMaxRects, packPortfolio, packRows, packShelf, packSkyline, packWith, packingMetrics, pairedRatio, portfolioCell, portfolioDefaultMembers, portfolioMembers, portfolioSetOf, portfolioStrategy, push, pushCascade, quantile, rectGrid, rectScattered, round3, roundMeasures, rows, shelf, skyline, spreadBand, spreadOf, strategies, strategy, supportsCell, tailShare, toPackInput, toRects, turnItem, turnPoint, turnPort, turnRegion, unsupportedOf, wordCloud };
|
|
7373
|
+
export { ASPECT_FIT_FACTORS, ASPECT_FIT_LEVELS, ASPECT_FIT_STAGE, DEFAULT_TOLERANCE, EPS, MAXRECTS_BANDS, MAXRECTS_RULES, MEASURE_SPECS, PORTFOLIO_FRAME_MS, PORTFOLIO_LEVELS, PORTFOLIO_SETS, PORTFOLIO_STAGE, RECORD_EPS, Rect, SHELF_FITS, SORT_ORDERS, SPREAD_THRESHOLDS, THIN_INPUTS, UnsupportedCell, Vector, aspectError, aspectFit, aspectFitStrategy, assertSupported, canonicalJson, cards, cellKey, cellOf, checkHard, checkInput, compareRects, composites, corpus, countCell, createContext, dashboard, edgeCases, fnv1a32, freeSpace, fromRects, gallery, generator, generators, hardBreaks, hashInput, isDeterministic, logSweep, lowestWindow, marginOf, maxrects, measure, measureChange, medianRatio, mulberry32, outputHash, pack, packFree, packMaxRects, packPortfolio, packRows, packShelf, packSkyline, packWith, packingMetrics, pairedRatio, portfolioCell, portfolioDefaultMembers, portfolioMembers, portfolioSetOf, portfolioStrategy, push, pushCascade, quantile, rectGrid, rectGridV2, rectScattered, round3, roundMeasures, rows, shelf, skyline, spreadBand, spreadOf, strategies, strategy, supportsCell, tailShare, toPackInput, toRects, turnItem, turnPoint, turnPort, turnRegion, unsupportedOf, wordCloud };
|
|
7019
7374
|
|
|
7020
7375
|
//# sourceMappingURL=index.js.map
|