@mark1russell7/packing 0.1.4 → 0.1.6
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.d.ts +115 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +575 -286
- package/build/index.js.map +1 -1
- package/package.json +2 -1
- package/src/check.ts +11 -5
- package/src/index.ts +18 -0
- package/src/measures.ts +363 -0
- package/src/metrics.ts +8 -20
- package/src/strategy.ts +6 -3
package/build/index.js
CHANGED
|
@@ -598,66 +598,6 @@ function pushCascade(placed, rects, options = {}) {
|
|
|
598
598
|
}
|
|
599
599
|
return [...moved];
|
|
600
600
|
}
|
|
601
|
-
// src/metrics.ts
|
|
602
|
-
/**
|
|
603
|
-
* This function measures a packing. It gives the hull and its aspect, the rows and the raggedness of the right side.
|
|
604
|
-
* It also gives the part of the hull that the rectangles cover, and the widest rectangle against the median width.
|
|
605
|
-
*
|
|
606
|
-
* @example
|
|
607
|
-
* ```ts
|
|
608
|
-
* const { utilization, raggedness } = packingMetrics(Rect.packed(cards));
|
|
609
|
-
* ```
|
|
610
|
-
*/
|
|
611
|
-
function packingMetrics(rects, options = {}) {
|
|
612
|
-
const tolerance = options.rowTolerance ?? 40;
|
|
613
|
-
const boxes = rects.filter((r) => r.size.x > 0 && r.size.y > 0);
|
|
614
|
-
if (boxes.length === 0) return {
|
|
615
|
-
count: 0,
|
|
616
|
-
hull: {
|
|
617
|
-
width: 0,
|
|
618
|
-
height: 0
|
|
619
|
-
},
|
|
620
|
-
hullAspect: 0,
|
|
621
|
-
rows: [],
|
|
622
|
-
raggedness: 0,
|
|
623
|
-
utilization: 0,
|
|
624
|
-
widestOverMedian: 0
|
|
625
|
-
};
|
|
626
|
-
const left = Math.min(...boxes.map((r) => r.position.x));
|
|
627
|
-
const top = Math.min(...boxes.map((r) => r.position.y));
|
|
628
|
-
const right = Math.max(...boxes.map((r) => r.right()));
|
|
629
|
-
const bottom = Math.max(...boxes.map((r) => r.bottom()));
|
|
630
|
-
const hull = {
|
|
631
|
-
width: right - left,
|
|
632
|
-
height: bottom - top
|
|
633
|
-
};
|
|
634
|
-
const rows = [];
|
|
635
|
-
for (const r of boxes.toSorted((a, b) => a.position.y - b.position.y)) {
|
|
636
|
-
const row = rows.at(-1);
|
|
637
|
-
if (row && Math.abs(r.position.y - row.top) <= tolerance) {
|
|
638
|
-
row.right = Math.max(row.right, r.right());
|
|
639
|
-
row.count += 1;
|
|
640
|
-
} else rows.push({
|
|
641
|
-
top: r.position.y,
|
|
642
|
-
right: r.right(),
|
|
643
|
-
count: 1
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
const rights = rows.map((row) => row.right);
|
|
647
|
-
const widths = boxes.map((r) => r.size.x).toSorted((a, b) => a - b);
|
|
648
|
-
const median = widths[Math.floor(widths.length / 2)] ?? 0;
|
|
649
|
-
const widest = widths.at(-1) ?? 0;
|
|
650
|
-
const area = boxes.reduce((sum, r) => sum + r.area(), 0);
|
|
651
|
-
return {
|
|
652
|
-
count: boxes.length,
|
|
653
|
-
hull,
|
|
654
|
-
hullAspect: hull.height > 0 ? hull.width / hull.height : 0,
|
|
655
|
-
rows: rows.map((row) => row.count),
|
|
656
|
-
raggedness: Math.max(...rights) - Math.min(...rights),
|
|
657
|
-
utilization: hull.width * hull.height > 0 ? area / (hull.width * hull.height) : 0,
|
|
658
|
-
widestOverMedian: median > 0 ? widest / median : 0
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
601
|
// src/contract.ts
|
|
662
602
|
/** The tolerance for the comparison of two numbers in an engine. */
|
|
663
603
|
const EPS = 1e-6;
|
|
@@ -750,28 +690,563 @@ function turnRegion(region, h) {
|
|
|
750
690
|
}
|
|
751
691
|
}
|
|
752
692
|
/**
|
|
753
|
-
* This function gives an item after a turn of 90 degrees clockwise, with the y axis down (decision 1 of
|
|
754
|
-
* `docs/PACKER.md`). The width and the height change places. The top-left corner, the id and the other fields stay.
|
|
755
|
-
* The ports, the shape and the margin turn with the item. The function does not change its argument.
|
|
693
|
+
* This function gives an item after a turn of 90 degrees clockwise, with the y axis down (decision 1 of
|
|
694
|
+
* `docs/PACKER.md`). The width and the height change places. The top-left corner, the id and the other fields stay.
|
|
695
|
+
* The ports, the shape and the margin turn with the item. The function does not change its argument.
|
|
696
|
+
*/
|
|
697
|
+
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;
|
|
715
|
+
}
|
|
716
|
+
// src/sha256.ts
|
|
717
|
+
/** The round constants of SHA-256. */
|
|
718
|
+
const K = new Uint32Array([
|
|
719
|
+
1116352408,
|
|
720
|
+
1899447441,
|
|
721
|
+
3049323471,
|
|
722
|
+
3921009573,
|
|
723
|
+
961987163,
|
|
724
|
+
1508970993,
|
|
725
|
+
2453635748,
|
|
726
|
+
2870763221,
|
|
727
|
+
3624381080,
|
|
728
|
+
310598401,
|
|
729
|
+
607225278,
|
|
730
|
+
1426881987,
|
|
731
|
+
1925078388,
|
|
732
|
+
2162078206,
|
|
733
|
+
2614888103,
|
|
734
|
+
3248222580,
|
|
735
|
+
3835390401,
|
|
736
|
+
4022224774,
|
|
737
|
+
264347078,
|
|
738
|
+
604807628,
|
|
739
|
+
770255983,
|
|
740
|
+
1249150122,
|
|
741
|
+
1555081692,
|
|
742
|
+
1996064986,
|
|
743
|
+
2554220882,
|
|
744
|
+
2821834349,
|
|
745
|
+
2952996808,
|
|
746
|
+
3210313671,
|
|
747
|
+
3336571891,
|
|
748
|
+
3584528711,
|
|
749
|
+
113926993,
|
|
750
|
+
338241895,
|
|
751
|
+
666307205,
|
|
752
|
+
773529912,
|
|
753
|
+
1294757372,
|
|
754
|
+
1396182291,
|
|
755
|
+
1695183700,
|
|
756
|
+
1986661051,
|
|
757
|
+
2177026350,
|
|
758
|
+
2456956037,
|
|
759
|
+
2730485921,
|
|
760
|
+
2820302411,
|
|
761
|
+
3259730800,
|
|
762
|
+
3345764771,
|
|
763
|
+
3516065817,
|
|
764
|
+
3600352804,
|
|
765
|
+
4094571909,
|
|
766
|
+
275423344,
|
|
767
|
+
430227734,
|
|
768
|
+
506948616,
|
|
769
|
+
659060556,
|
|
770
|
+
883997877,
|
|
771
|
+
958139571,
|
|
772
|
+
1322822218,
|
|
773
|
+
1537002063,
|
|
774
|
+
1747873779,
|
|
775
|
+
1955562222,
|
|
776
|
+
2024104815,
|
|
777
|
+
2227730452,
|
|
778
|
+
2361852424,
|
|
779
|
+
2428436474,
|
|
780
|
+
2756734187,
|
|
781
|
+
3204031479,
|
|
782
|
+
3329325298
|
|
783
|
+
]);
|
|
784
|
+
/** The first hash value of SHA-256. */
|
|
785
|
+
const H0 = [
|
|
786
|
+
1779033703,
|
|
787
|
+
3144134277,
|
|
788
|
+
1013904242,
|
|
789
|
+
2773480762,
|
|
790
|
+
1359893119,
|
|
791
|
+
2600822924,
|
|
792
|
+
528734635,
|
|
793
|
+
1541459225
|
|
794
|
+
];
|
|
795
|
+
/** This function turns a 32-bit word to the right by `n` bits. */
|
|
796
|
+
const rotr = (x, n) => x >>> n | x << 32 - n;
|
|
797
|
+
/**
|
|
798
|
+
* This function gives the UTF-8 bytes of a string. A lone surrogate becomes U+FFFD, as with `TextEncoder`. The
|
|
799
|
+
* package has no `TextEncoder` type, because its code operates on each runtime with no global types.
|
|
800
|
+
*/
|
|
801
|
+
function utf8(text) {
|
|
802
|
+
const out = [];
|
|
803
|
+
for (let i = 0; i < text.length; i++) {
|
|
804
|
+
let c = text.charCodeAt(i);
|
|
805
|
+
if (c >= 55296 && c <= 56319 && i + 1 < text.length) {
|
|
806
|
+
const next = text.charCodeAt(i + 1);
|
|
807
|
+
if (next >= 56320 && next <= 57343) {
|
|
808
|
+
c = 65536 + (c - 55296 << 10) + (next - 56320);
|
|
809
|
+
i++;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (c >= 55296 && c <= 57343) c = 65533;
|
|
813
|
+
if (c < 128) out.push(c);
|
|
814
|
+
else if (c < 2048) out.push(192 | c >> 6, 128 | c & 63);
|
|
815
|
+
else if (c < 65536) out.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
|
|
816
|
+
else out.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
|
|
817
|
+
}
|
|
818
|
+
return Uint8Array.from(out);
|
|
819
|
+
}
|
|
820
|
+
/** This function gives the SHA-256 of the UTF-8 bytes of a string, as 64 lowercase hex characters. */
|
|
821
|
+
function sha256Hex(text) {
|
|
822
|
+
const bytes = utf8(text);
|
|
823
|
+
const length = bytes.length;
|
|
824
|
+
const padded = new Uint8Array(((length + 9 + 63) / 64 | 0) * 64);
|
|
825
|
+
padded.set(bytes);
|
|
826
|
+
padded[length] = 128;
|
|
827
|
+
const view = new DataView(padded.buffer);
|
|
828
|
+
const bits = length * 8;
|
|
829
|
+
view.setUint32(padded.length - 8, Math.floor(bits / 4294967296));
|
|
830
|
+
view.setUint32(padded.length - 4, bits >>> 0);
|
|
831
|
+
const hash = [...H0];
|
|
832
|
+
const w = /* @__PURE__ */ new Uint32Array(64);
|
|
833
|
+
for (let block = 0; block < padded.length; block += 64) {
|
|
834
|
+
for (let t = 0; t < 16; t++) w[t] = view.getUint32(block + t * 4);
|
|
835
|
+
for (let t = 16; t < 64; t++) {
|
|
836
|
+
const a = w[t - 15];
|
|
837
|
+
const b = w[t - 2];
|
|
838
|
+
const s0 = rotr(a, 7) ^ rotr(a, 18) ^ a >>> 3;
|
|
839
|
+
const s1 = rotr(b, 17) ^ rotr(b, 19) ^ b >>> 10;
|
|
840
|
+
w[t] = w[t - 16] + s0 + w[t - 7] + s1 | 0;
|
|
841
|
+
}
|
|
842
|
+
let [a, b, c, d, e, f, g, h] = hash;
|
|
843
|
+
for (let t = 0; t < 64; t++) {
|
|
844
|
+
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
845
|
+
const ch = e & f ^ ~e & g;
|
|
846
|
+
const t1 = h + s1 + ch + K[t] + w[t] | 0;
|
|
847
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) | 0;
|
|
848
|
+
h = g;
|
|
849
|
+
g = f;
|
|
850
|
+
f = e;
|
|
851
|
+
e = d + t1 | 0;
|
|
852
|
+
d = c;
|
|
853
|
+
c = b;
|
|
854
|
+
b = a;
|
|
855
|
+
a = t1 + t2 | 0;
|
|
856
|
+
}
|
|
857
|
+
hash[0] = hash[0] + a | 0;
|
|
858
|
+
hash[1] = hash[1] + b | 0;
|
|
859
|
+
hash[2] = hash[2] + c | 0;
|
|
860
|
+
hash[3] = hash[3] + d | 0;
|
|
861
|
+
hash[4] = hash[4] + e | 0;
|
|
862
|
+
hash[5] = hash[5] + f | 0;
|
|
863
|
+
hash[6] = hash[6] + g | 0;
|
|
864
|
+
hash[7] = hash[7] + h | 0;
|
|
865
|
+
}
|
|
866
|
+
return hash.map((x) => (x >>> 0).toString(16).padStart(8, "0")).join("");
|
|
867
|
+
}
|
|
868
|
+
// src/layout-input.ts
|
|
869
|
+
/** This function gives the `fnv1a32` hash of the UTF-8 bytes of a string, as `docs/FORMAT.md` gives it. */
|
|
870
|
+
function fnv1a32(text) {
|
|
871
|
+
let h = 2166136261;
|
|
872
|
+
for (const b of utf8(text)) h = Math.imul(h ^ b, 16777619) >>> 0;
|
|
873
|
+
return h;
|
|
874
|
+
}
|
|
875
|
+
/** This function rounds the numbers of a file to 3 decimals. A half goes up, and the value `-0` becomes 0. */
|
|
876
|
+
function round3(x) {
|
|
877
|
+
const v = Math.round(x * 1e3) / 1e3;
|
|
878
|
+
return Object.is(v, -0) ? 0 : v;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* This function gives the canonical form of a value: sorted keys, no whitespace and the numbers of `JSON.stringify`
|
|
882
|
+
* after `round3`. A key with the value `undefined` is not in the form.
|
|
883
|
+
*/
|
|
884
|
+
function canonicalJson(value) {
|
|
885
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
886
|
+
if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
887
|
+
return JSON.stringify(typeof value === "number" ? round3(value) : value);
|
|
888
|
+
}
|
|
889
|
+
/** 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
|
+
function hashInput(file) {
|
|
891
|
+
const rest = { ...file };
|
|
892
|
+
delete rest.hash;
|
|
893
|
+
return sha256Hex(canonicalJson(rest)).slice(0, 16);
|
|
894
|
+
}
|
|
895
|
+
/** 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`. */
|
|
896
|
+
function countCell(count) {
|
|
897
|
+
if (count <= 3) return "1";
|
|
898
|
+
if (count <= 31) return "10";
|
|
899
|
+
if (count <= 316) return "100";
|
|
900
|
+
if (count <= 3162) return "1k";
|
|
901
|
+
return "10k";
|
|
902
|
+
}
|
|
903
|
+
/** This function gives the packing input of a file. The boxes become the items, and the edges go. */
|
|
904
|
+
function toPackInput(file) {
|
|
905
|
+
const input = {
|
|
906
|
+
container: file.container,
|
|
907
|
+
items: file.boxes
|
|
908
|
+
};
|
|
909
|
+
if (file.obstacles !== void 0) input.obstacles = file.obstacles;
|
|
910
|
+
if (file.gap !== void 0) input.gap = file.gap;
|
|
911
|
+
return input;
|
|
912
|
+
}
|
|
913
|
+
// src/measures.ts
|
|
914
|
+
const spec = (id, family, unit, direction, min, max) => ({
|
|
915
|
+
id,
|
|
916
|
+
family,
|
|
917
|
+
unit,
|
|
918
|
+
direction,
|
|
919
|
+
min,
|
|
920
|
+
max
|
|
921
|
+
});
|
|
922
|
+
/** The facts of each measure, by id. `docs/MEASURES.md` gives the full definitions. */
|
|
923
|
+
const MEASURE_SPECS = Object.freeze({
|
|
924
|
+
"pack.area": spec("pack.area", "density", "square units", "lower", 0, Infinity),
|
|
925
|
+
"pack.fill": spec("pack.fill", "density", "share", "higher", 0, 1),
|
|
926
|
+
"pack.whitespace": spec("pack.whitespace", "density", "share", "lower", 0, 1),
|
|
927
|
+
"pack.aspect": spec("pack.aspect", "aspect", "ratio", "lower", 1, Infinity),
|
|
928
|
+
"pack.aspectError": spec("pack.aspectError", "aspect", "natural log", "lower", 0, Infinity),
|
|
929
|
+
"pack.order.inversions": spec("pack.order.inversions", "order", "share", "lower", 0, 1),
|
|
930
|
+
"pack.order.breaks": spec("pack.order.breaks", "order", "count", "lower", 0, Infinity),
|
|
931
|
+
"pack.raggedness": spec("pack.raggedness", "raggedness", "share", "lower", 0, 1),
|
|
932
|
+
"pack.overflow": spec("pack.overflow", "overflow", "count", "lower", 0, Infinity),
|
|
933
|
+
"pack.stability.moved": spec("pack.stability.moved", "stability", "share", "lower", 0, 1),
|
|
934
|
+
"pack.stability.mean": spec("pack.stability.mean", "stability", "container widths", "lower", 0, Infinity),
|
|
935
|
+
"pack.stability.q95": spec("pack.stability.q95", "stability", "container widths", "lower", 0, Infinity),
|
|
936
|
+
"pack.deterministic": spec("pack.deterministic", "determinism", "boolean", "higher", 0, 1),
|
|
937
|
+
"pack.search.widths": spec("pack.search.widths", "speed", "count", "lower", 0, Infinity),
|
|
938
|
+
"pack.search.ms": spec("pack.search.ms", "speed", "milliseconds", "lower", 0, Infinity)
|
|
939
|
+
});
|
|
940
|
+
/** This function gives the smallest rectangle around the boxes. No box gives a rectangle of size 0 at 0, 0. */
|
|
941
|
+
function hullOf(boxes) {
|
|
942
|
+
if (boxes.length === 0) return {
|
|
943
|
+
x: 0,
|
|
944
|
+
y: 0,
|
|
945
|
+
w: 0,
|
|
946
|
+
h: 0
|
|
947
|
+
};
|
|
948
|
+
let left = Infinity;
|
|
949
|
+
let top = Infinity;
|
|
950
|
+
let right = -Infinity;
|
|
951
|
+
let bottom = -Infinity;
|
|
952
|
+
for (const b of boxes) {
|
|
953
|
+
left = Math.min(left, b.x);
|
|
954
|
+
top = Math.min(top, b.y);
|
|
955
|
+
right = Math.max(right, b.x + b.w);
|
|
956
|
+
bottom = Math.max(bottom, b.y + b.h);
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
x: left,
|
|
960
|
+
y: top,
|
|
961
|
+
w: right - left,
|
|
962
|
+
h: bottom - top
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* This function puts the boxes in rows. It sorts the boxes by their top, and keeps the order of the list for equal tops.
|
|
967
|
+
* A box joins the last row when its top is within `tolerance` of the top of the first box of that row.
|
|
968
|
+
*/
|
|
969
|
+
function rowsOf$1(boxes, tolerance) {
|
|
970
|
+
const rows = [];
|
|
971
|
+
for (const b of boxes.toSorted((p, q) => p.y - q.y)) {
|
|
972
|
+
const row = rows.at(-1);
|
|
973
|
+
if (row && Math.abs(b.y - row.top) <= tolerance) {
|
|
974
|
+
row.right = Math.max(row.right, b.x + b.w);
|
|
975
|
+
row.count += 1;
|
|
976
|
+
} else rows.push({
|
|
977
|
+
top: b.y,
|
|
978
|
+
right: b.x + b.w,
|
|
979
|
+
count: 1
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
return rows;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* This function gives the placed boxes of an output, in the order of the items of the input. A box has the size of its
|
|
986
|
+
* item after the turn. The list has no item without a placement, and no item with a width or a height of 0.
|
|
987
|
+
*/
|
|
988
|
+
function boxesOf$1(input, output) {
|
|
989
|
+
const placements = /* @__PURE__ */ new Map();
|
|
990
|
+
for (const p of output.placements) if (!placements.has(p.id)) placements.set(p.id, p);
|
|
991
|
+
const boxes = [];
|
|
992
|
+
for (const [index, item] of input.items.entries()) {
|
|
993
|
+
const p = placements.get(item.id);
|
|
994
|
+
if (p === void 0 || !(item.w > 0 && item.h > 0)) continue;
|
|
995
|
+
const turned = p.turn === 90;
|
|
996
|
+
boxes.push({
|
|
997
|
+
x: p.x,
|
|
998
|
+
y: p.y,
|
|
999
|
+
w: turned ? item.h : item.w,
|
|
1000
|
+
h: turned ? item.w : item.h,
|
|
1001
|
+
rank: item.rank ?? index
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
return boxes;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* This function tells if box `a` comes before box `b` in the reading order. Two boxes whose heights overlap by more than
|
|
1008
|
+
* `RECORD_EPS` are on one line, and the left box comes first. Otherwise the upper box comes first.
|
|
1009
|
+
*/
|
|
1010
|
+
function readsBefore(a, b) {
|
|
1011
|
+
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;
|
|
1012
|
+
return a.y < b.y;
|
|
1013
|
+
}
|
|
1014
|
+
/** This function sums one measure over the stages of a trace. A trace without the measure gives 0. */
|
|
1015
|
+
function traceSum(output, id) {
|
|
1016
|
+
let sum = 0;
|
|
1017
|
+
for (const stage of output.trace) sum += stage.measures[id] ?? 0;
|
|
1018
|
+
return sum;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* This function gives the measures of one layout, by the ids of `docs/MEASURES.md`. The values are not rounded. A
|
|
1022
|
+
* record rounds them with `roundMeasures`.
|
|
1023
|
+
*
|
|
1024
|
+
* - The boxes are the placed items with a width and a height above 0. The hull is the smallest rectangle around them.
|
|
1025
|
+
* - An empty layout gives the area 0, the aspect 1 and the aspect error 0. Its fill is 0 and its whitespace is 1.
|
|
1026
|
+
* - The time `pack.search.ms` comes from the trace. It is not part of the determinism rule.
|
|
1027
|
+
*
|
|
1028
|
+
* @example
|
|
1029
|
+
* ```ts
|
|
1030
|
+
* const m = measure(input, packWith("shelf", input));
|
|
1031
|
+
* m["pack.fill"]; // the area of the boxes divided by the area of the hull
|
|
1032
|
+
* ```
|
|
1033
|
+
*/
|
|
1034
|
+
function measure(input, output, options = {}) {
|
|
1035
|
+
const boxes = boxesOf$1(input, output);
|
|
1036
|
+
const hull = hullOf(boxes);
|
|
1037
|
+
const hullArea = hull.w * hull.h;
|
|
1038
|
+
const boxArea = boxes.reduce((sum, b) => sum + b.w * b.h, 0);
|
|
1039
|
+
const fill = hullArea > 0 ? boxArea / hullArea : 0;
|
|
1040
|
+
const empty = boxes.length === 0;
|
|
1041
|
+
const target = input.container.aspect ?? 1;
|
|
1042
|
+
let pairs = 0;
|
|
1043
|
+
let inversions = 0;
|
|
1044
|
+
for (let i = 0; i < boxes.length; i++) for (let j = i + 1; j < boxes.length; j++) {
|
|
1045
|
+
const a = boxes[i];
|
|
1046
|
+
const b = boxes[j];
|
|
1047
|
+
if (a.rank === b.rank) continue;
|
|
1048
|
+
pairs += 1;
|
|
1049
|
+
const first = a.rank < b.rank ? a : b;
|
|
1050
|
+
if (readsBefore(first === a ? b : a, first)) inversions += 1;
|
|
1051
|
+
}
|
|
1052
|
+
const byRank = boxes.toSorted((a, b) => a.rank - b.rank);
|
|
1053
|
+
let breaks = 0;
|
|
1054
|
+
for (let k = 1; k < byRank.length; k++) {
|
|
1055
|
+
const prev = byRank[k - 1];
|
|
1056
|
+
const next = byRank[k];
|
|
1057
|
+
if (prev.rank !== next.rank && readsBefore(next, prev)) breaks += 1;
|
|
1058
|
+
}
|
|
1059
|
+
const rights = rowsOf$1(boxes, options.rowTolerance ?? quantileOrZero(boxes.map((b) => b.h), .5) / 2).map((r) => r.right);
|
|
1060
|
+
const spread = rights.length > 0 ? Math.max(...rights) - Math.min(...rights) : 0;
|
|
1061
|
+
return {
|
|
1062
|
+
"pack.area": hullArea,
|
|
1063
|
+
"pack.fill": fill,
|
|
1064
|
+
"pack.whitespace": 1 - fill,
|
|
1065
|
+
"pack.aspect": empty ? 1 : Math.max(hull.w / hull.h, hull.h / hull.w),
|
|
1066
|
+
"pack.aspectError": empty ? 0 : aspectError(hull.w, hull.h, target),
|
|
1067
|
+
"pack.order.inversions": pairs > 0 ? inversions / pairs : 0,
|
|
1068
|
+
"pack.order.breaks": breaks,
|
|
1069
|
+
"pack.raggedness": hull.w > 0 ? spread / hull.w : 0,
|
|
1070
|
+
"pack.overflow": new Set(output.overflow).size,
|
|
1071
|
+
"pack.search.widths": traceSum(output, "pack.search.widths"),
|
|
1072
|
+
"pack.search.ms": traceSum(output, "pack.search.ms")
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* This function gives the stability measures of a change pair: the run on the base input and the run on the changed
|
|
1077
|
+
* input. The items match by id. The values are not rounded.
|
|
1078
|
+
*
|
|
1079
|
+
* - An item stays when both inputs have it and both outputs place it. The added and the removed items do not count.
|
|
1080
|
+
* - A staying item moved when its `x` or its `y` changed by more than `RECORD_EPS`, or when its turn changed.
|
|
1081
|
+
* - The distance of an item is the length of the move of its top-left corner. The unit is the width `W`.
|
|
1082
|
+
* - `W` is the larger width of the 2 output sizes. A `W` of 0 becomes 1, so the unit is then the unit of the input.
|
|
1083
|
+
* - No staying item gives 0 for each measure.
|
|
1084
|
+
*/
|
|
1085
|
+
function measureChange(baseInput, baseOutput, changeInput, changeOutput) {
|
|
1086
|
+
const placed = (input, output) => {
|
|
1087
|
+
const ids = new Set(input.items.map((i) => i.id));
|
|
1088
|
+
const map = /* @__PURE__ */ new Map();
|
|
1089
|
+
for (const p of output.placements) if (ids.has(p.id) && !map.has(p.id)) map.set(p.id, p);
|
|
1090
|
+
return map;
|
|
1091
|
+
};
|
|
1092
|
+
const before = placed(baseInput, baseOutput);
|
|
1093
|
+
const after = placed(changeInput, changeOutput);
|
|
1094
|
+
const width = Math.max(baseOutput.size.w, changeOutput.size.w);
|
|
1095
|
+
const unit = width > 0 ? width : 1;
|
|
1096
|
+
const distances = [];
|
|
1097
|
+
let moved = 0;
|
|
1098
|
+
for (const [id, p] of before) {
|
|
1099
|
+
const q = after.get(id);
|
|
1100
|
+
if (q === void 0) continue;
|
|
1101
|
+
const dx = q.x - p.x;
|
|
1102
|
+
const dy = q.y - p.y;
|
|
1103
|
+
if (Math.abs(dx) > .001 || Math.abs(dy) > .001 || (p.turn ?? 0) !== (q.turn ?? 0)) moved += 1;
|
|
1104
|
+
distances.push(Math.hypot(dx, dy) / unit);
|
|
1105
|
+
}
|
|
1106
|
+
if (distances.length === 0) return {
|
|
1107
|
+
"pack.stability.moved": 0,
|
|
1108
|
+
"pack.stability.mean": 0,
|
|
1109
|
+
"pack.stability.q95": 0
|
|
1110
|
+
};
|
|
1111
|
+
return {
|
|
1112
|
+
"pack.stability.moved": moved / distances.length,
|
|
1113
|
+
"pack.stability.mean": distances.reduce((sum, d) => sum + d, 0) / distances.length,
|
|
1114
|
+
"pack.stability.q95": quantile(distances, .95)
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* This function gives the measure `pack.deterministic` of two runs with the same input, options and seed. It gives
|
|
1119
|
+
* `true` when the runs have the same placements, overflow, size and moved obstacles.
|
|
1120
|
+
*
|
|
1121
|
+
* - The placements match by id, so their order in the list does not count. The numbers must be equal.
|
|
1122
|
+
* - The trace is not part of the rule, because its times change each time.
|
|
1123
|
+
*/
|
|
1124
|
+
function isDeterministic(first, second) {
|
|
1125
|
+
const key = (list) => new Map(list.map((e) => [e.id, e]));
|
|
1126
|
+
const samePlacements = (a, b) => {
|
|
1127
|
+
if (a.length !== b.length) return false;
|
|
1128
|
+
const other = key(b);
|
|
1129
|
+
return a.every((p) => {
|
|
1130
|
+
const q = other.get(p.id);
|
|
1131
|
+
return q !== void 0 && q.x === p.x && q.y === p.y && (q.turn ?? 0) === (p.turn ?? 0);
|
|
1132
|
+
});
|
|
1133
|
+
};
|
|
1134
|
+
const sameMoved = (a, b) => {
|
|
1135
|
+
if (a.length !== b.length) return false;
|
|
1136
|
+
const other = key(b);
|
|
1137
|
+
return a.every((m) => {
|
|
1138
|
+
const n = other.get(m.id);
|
|
1139
|
+
return n !== void 0 && n.x === m.x && n.y === m.y;
|
|
1140
|
+
});
|
|
1141
|
+
};
|
|
1142
|
+
const overflowA = new Set(first.overflow);
|
|
1143
|
+
const overflowB = new Set(second.overflow);
|
|
1144
|
+
return first.size.w === second.size.w && first.size.h === second.size.h && overflowA.size === overflowB.size && [...overflowA].every((id) => overflowB.has(id)) && samePlacements(first.placements, second.placements) && sameMoved(first.moved, second.moved);
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* This function rounds each number of a record of measures with `round3`, as `docs/FORMAT.md` tells. A half goes up,
|
|
1148
|
+
* and `-0` becomes 0. The function keeps the other values, and it does not change its argument.
|
|
1149
|
+
*/
|
|
1150
|
+
function roundMeasures(measures) {
|
|
1151
|
+
const out = {};
|
|
1152
|
+
for (const [id, value] of Object.entries(measures)) out[id] = typeof value === "number" ? round3(value) : value;
|
|
1153
|
+
return out;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* This function gives the paired ratio of a measure on one input: the value of the candidate against the value of the
|
|
1157
|
+
* current default. A ratio above 1 is worse, in the direction of the measure.
|
|
1158
|
+
*
|
|
1159
|
+
* - For `lower`, the ratio is `candidate / current`. For `higher`, it is `current / candidate`.
|
|
1160
|
+
* - When both values are 0, the ratio is 1. When only the divisor is 0, the ratio is `Infinity`.
|
|
1161
|
+
* - The values are 0 or more. A negative value or `NaN` throws a `RangeError`.
|
|
1162
|
+
*/
|
|
1163
|
+
function pairedRatio(candidate, current, direction) {
|
|
1164
|
+
if (!(candidate >= 0 && current >= 0)) throw new RangeError("A paired ratio needs two values of 0 or more.");
|
|
1165
|
+
const top = direction === "lower" ? candidate : current;
|
|
1166
|
+
const bottom = direction === "lower" ? current : candidate;
|
|
1167
|
+
if (bottom === 0) return top === 0 ? 1 : Infinity;
|
|
1168
|
+
return top / bottom;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* This function gives the quantile `q` of a list of numbers, with linear interpolation between the sorted values. The
|
|
1172
|
+
* position in the sorted list is `(n - 1) * q`. An empty list or a `q` outside 0 to 1 throws a `RangeError`.
|
|
1173
|
+
*/
|
|
1174
|
+
function quantile(values, q) {
|
|
1175
|
+
if (values.length === 0) throw new RangeError("A quantile needs one value or more.");
|
|
1176
|
+
if (!(q >= 0 && q <= 1)) throw new RangeError("A quantile needs a q from 0 to 1.");
|
|
1177
|
+
const sorted = values.toSorted((a, b) => a - b);
|
|
1178
|
+
const at = (sorted.length - 1) * q;
|
|
1179
|
+
const low = Math.floor(at);
|
|
1180
|
+
const part = at - low;
|
|
1181
|
+
const a = sorted[low];
|
|
1182
|
+
if (part === 0) return a;
|
|
1183
|
+
const b = sorted[low + 1];
|
|
1184
|
+
if (a === b) return a;
|
|
1185
|
+
return a + part * (b - a);
|
|
1186
|
+
}
|
|
1187
|
+
/** This function gives the quantile `q` of a list, and 0 for an empty list. */
|
|
1188
|
+
function quantileOrZero(values, q) {
|
|
1189
|
+
return values.length === 0 ? 0 : quantile(values, q);
|
|
1190
|
+
}
|
|
1191
|
+
/** This function gives the median of the paired ratios of a corpus: the quantile 0.5. It is not a ratio of two medians. */
|
|
1192
|
+
function medianRatio(ratios) {
|
|
1193
|
+
return quantile(ratios, .5);
|
|
1194
|
+
}
|
|
1195
|
+
/** This function gives the share of the paired ratios above a limit. An empty list throws a `RangeError`. */
|
|
1196
|
+
function tailShare(ratios, limit) {
|
|
1197
|
+
if (ratios.length === 0) throw new RangeError("A tail share needs one ratio or more.");
|
|
1198
|
+
return ratios.filter((r) => r > limit).length / ratios.length;
|
|
1199
|
+
}
|
|
1200
|
+
// src/metrics.ts
|
|
1201
|
+
/**
|
|
1202
|
+
* This function measures a packing. It gives the hull and its aspect, the rows and the raggedness of the right side.
|
|
1203
|
+
* It also gives the part of the hull that the rectangles cover, and the widest rectangle against the median width.
|
|
1204
|
+
*
|
|
1205
|
+
* @example
|
|
1206
|
+
* ```ts
|
|
1207
|
+
* const { utilization, raggedness } = packingMetrics(Rect.packed(cards));
|
|
1208
|
+
* ```
|
|
756
1209
|
*/
|
|
757
|
-
function
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1210
|
+
function packingMetrics(rects, options = {}) {
|
|
1211
|
+
const tolerance = options.rowTolerance ?? 40;
|
|
1212
|
+
const boxes = rects.filter((r) => r.size.x > 0 && r.size.y > 0).map((r) => ({
|
|
1213
|
+
x: r.position.x,
|
|
1214
|
+
y: r.position.y,
|
|
1215
|
+
w: r.size.x,
|
|
1216
|
+
h: r.size.y
|
|
1217
|
+
}));
|
|
1218
|
+
if (boxes.length === 0) return {
|
|
1219
|
+
count: 0,
|
|
1220
|
+
hull: {
|
|
1221
|
+
width: 0,
|
|
1222
|
+
height: 0
|
|
1223
|
+
},
|
|
1224
|
+
hullAspect: 0,
|
|
1225
|
+
rows: [],
|
|
1226
|
+
raggedness: 0,
|
|
1227
|
+
utilization: 0,
|
|
1228
|
+
widestOverMedian: 0
|
|
1229
|
+
};
|
|
1230
|
+
const box = hullOf(boxes);
|
|
1231
|
+
const hull = {
|
|
1232
|
+
width: box.w,
|
|
1233
|
+
height: box.h
|
|
1234
|
+
};
|
|
1235
|
+
const rows = rowsOf$1(boxes, tolerance);
|
|
1236
|
+
const rights = rows.map((row) => row.right);
|
|
1237
|
+
const widths = boxes.map((r) => r.w).toSorted((a, b) => a - b);
|
|
1238
|
+
const median = widths[Math.floor(widths.length / 2)] ?? 0;
|
|
1239
|
+
const widest = widths.at(-1) ?? 0;
|
|
1240
|
+
const area = boxes.reduce((sum, r) => sum + r.w * r.h, 0);
|
|
1241
|
+
return {
|
|
1242
|
+
count: boxes.length,
|
|
1243
|
+
hull,
|
|
1244
|
+
hullAspect: hull.height > 0 ? hull.width / hull.height : 0,
|
|
1245
|
+
rows: rows.map((row) => row.count),
|
|
1246
|
+
raggedness: Math.max(...rights) - Math.min(...rights),
|
|
1247
|
+
utilization: hull.width * hull.height > 0 ? area / (hull.width * hull.height) : 0,
|
|
1248
|
+
widestOverMedian: median > 0 ? widest / median : 0
|
|
762
1249
|
};
|
|
763
|
-
if (item.ports) turned.ports = item.ports.map((p) => turnPort(p, item.h));
|
|
764
|
-
if (item.shape) turned.shape = turnRegion(item.shape, item.h);
|
|
765
|
-
if (item.margin !== void 0 && typeof item.margin !== "number") {
|
|
766
|
-
const m = item.margin;
|
|
767
|
-
turned.margin = {
|
|
768
|
-
top: m.left,
|
|
769
|
-
right: m.top,
|
|
770
|
-
bottom: m.right,
|
|
771
|
-
left: m.bottom
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
return turned;
|
|
775
1250
|
}
|
|
776
1251
|
// src/strategy.ts
|
|
777
1252
|
/**
|
|
@@ -1575,11 +2050,13 @@ function overlap(a, b, tolerance) {
|
|
|
1575
2050
|
* This function counts the breaks of the 4 hard rules of the packer for an output.
|
|
1576
2051
|
*
|
|
1577
2052
|
* - `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`.
|
|
1578
|
-
* - `in-container`: each item with its margin that goes out of a fixed side by more than `tolerance.overlap`.
|
|
2053
|
+
* - `in-container`: each item with its margin that goes out of a fixed side by more than `tolerance.overlap`. The inset of
|
|
2054
|
+
* the container makes each fixed side smaller (decision 3 of `docs/PACKER.md`).
|
|
1579
2055
|
* - `locked-kept`: each locked item that moved by more than `RECORD_EPS`, or that has no position.
|
|
1580
2056
|
* - `complete`: each item with no position that is not in the overflow.
|
|
1581
2057
|
*
|
|
1582
|
-
* The rules read a shape of the container or of an
|
|
2058
|
+
* The rules read a shape of the container, of an obstacle or of an item as its bounding box (decision 5). The bounding box
|
|
2059
|
+
* of an item shape is relative to the position of the item. The result has no tolerance in it.
|
|
1583
2060
|
*/
|
|
1584
2061
|
function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
1585
2062
|
const tol = tolerance.overlap;
|
|
@@ -1605,11 +2082,19 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
|
1605
2082
|
}
|
|
1606
2083
|
const placed = p.turn === 90 ? turnItem(item) : item;
|
|
1607
2084
|
const m = marginOf(placed);
|
|
2085
|
+
const area = placed.shape === void 0 ? {
|
|
2086
|
+
x: 0,
|
|
2087
|
+
y: 0,
|
|
2088
|
+
w: placed.w,
|
|
2089
|
+
h: placed.h
|
|
2090
|
+
} : boundsOf(placed.shape);
|
|
2091
|
+
const l = p.x + area.x;
|
|
2092
|
+
const t = p.y + area.y;
|
|
1608
2093
|
boxes.push({
|
|
1609
|
-
l:
|
|
1610
|
-
t:
|
|
1611
|
-
r:
|
|
1612
|
-
b:
|
|
2094
|
+
l: l - m.left,
|
|
2095
|
+
t: t - m.top,
|
|
2096
|
+
r: l + area.w + m.right,
|
|
2097
|
+
b: t + area.h + m.bottom
|
|
1613
2098
|
});
|
|
1614
2099
|
}
|
|
1615
2100
|
const moved = new Map(output.moved.map((m) => [m.id, m]));
|
|
@@ -1633,212 +2118,16 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
|
1633
2118
|
const y = shape?.y ?? 0;
|
|
1634
2119
|
const w = shape?.w ?? container.w ?? Infinity;
|
|
1635
2120
|
const h = shape?.h ?? container.h ?? Infinity;
|
|
2121
|
+
const inset = container.inset ?? 0;
|
|
1636
2122
|
const fixedW = container.fixed === "size" || container.fixed === "width";
|
|
1637
2123
|
const fixedH = container.fixed === "size" || container.fixed === "height";
|
|
1638
2124
|
for (const box of boxes) {
|
|
1639
|
-
const outX = fixedW && (box.l < x - tol || box.r > x + w + tol);
|
|
1640
|
-
const outY = fixedH && (box.t < y - tol || box.b > y + h + tol);
|
|
2125
|
+
const outX = fixedW && (box.l < x + inset - tol || box.r > x + w - inset + tol);
|
|
2126
|
+
const outY = fixedH && (box.t < y + inset - tol || box.b > y + h - inset + tol);
|
|
1641
2127
|
if (outX || outY) breaks["in-container"] += 1;
|
|
1642
2128
|
}
|
|
1643
2129
|
return breaks;
|
|
1644
2130
|
}
|
|
1645
|
-
// src/sha256.ts
|
|
1646
|
-
/** The round constants of SHA-256. */
|
|
1647
|
-
const K = new Uint32Array([
|
|
1648
|
-
1116352408,
|
|
1649
|
-
1899447441,
|
|
1650
|
-
3049323471,
|
|
1651
|
-
3921009573,
|
|
1652
|
-
961987163,
|
|
1653
|
-
1508970993,
|
|
1654
|
-
2453635748,
|
|
1655
|
-
2870763221,
|
|
1656
|
-
3624381080,
|
|
1657
|
-
310598401,
|
|
1658
|
-
607225278,
|
|
1659
|
-
1426881987,
|
|
1660
|
-
1925078388,
|
|
1661
|
-
2162078206,
|
|
1662
|
-
2614888103,
|
|
1663
|
-
3248222580,
|
|
1664
|
-
3835390401,
|
|
1665
|
-
4022224774,
|
|
1666
|
-
264347078,
|
|
1667
|
-
604807628,
|
|
1668
|
-
770255983,
|
|
1669
|
-
1249150122,
|
|
1670
|
-
1555081692,
|
|
1671
|
-
1996064986,
|
|
1672
|
-
2554220882,
|
|
1673
|
-
2821834349,
|
|
1674
|
-
2952996808,
|
|
1675
|
-
3210313671,
|
|
1676
|
-
3336571891,
|
|
1677
|
-
3584528711,
|
|
1678
|
-
113926993,
|
|
1679
|
-
338241895,
|
|
1680
|
-
666307205,
|
|
1681
|
-
773529912,
|
|
1682
|
-
1294757372,
|
|
1683
|
-
1396182291,
|
|
1684
|
-
1695183700,
|
|
1685
|
-
1986661051,
|
|
1686
|
-
2177026350,
|
|
1687
|
-
2456956037,
|
|
1688
|
-
2730485921,
|
|
1689
|
-
2820302411,
|
|
1690
|
-
3259730800,
|
|
1691
|
-
3345764771,
|
|
1692
|
-
3516065817,
|
|
1693
|
-
3600352804,
|
|
1694
|
-
4094571909,
|
|
1695
|
-
275423344,
|
|
1696
|
-
430227734,
|
|
1697
|
-
506948616,
|
|
1698
|
-
659060556,
|
|
1699
|
-
883997877,
|
|
1700
|
-
958139571,
|
|
1701
|
-
1322822218,
|
|
1702
|
-
1537002063,
|
|
1703
|
-
1747873779,
|
|
1704
|
-
1955562222,
|
|
1705
|
-
2024104815,
|
|
1706
|
-
2227730452,
|
|
1707
|
-
2361852424,
|
|
1708
|
-
2428436474,
|
|
1709
|
-
2756734187,
|
|
1710
|
-
3204031479,
|
|
1711
|
-
3329325298
|
|
1712
|
-
]);
|
|
1713
|
-
/** The first hash value of SHA-256. */
|
|
1714
|
-
const H0 = [
|
|
1715
|
-
1779033703,
|
|
1716
|
-
3144134277,
|
|
1717
|
-
1013904242,
|
|
1718
|
-
2773480762,
|
|
1719
|
-
1359893119,
|
|
1720
|
-
2600822924,
|
|
1721
|
-
528734635,
|
|
1722
|
-
1541459225
|
|
1723
|
-
];
|
|
1724
|
-
/** This function turns a 32-bit word to the right by `n` bits. */
|
|
1725
|
-
const rotr = (x, n) => x >>> n | x << 32 - n;
|
|
1726
|
-
/**
|
|
1727
|
-
* This function gives the UTF-8 bytes of a string. A lone surrogate becomes U+FFFD, as with `TextEncoder`. The
|
|
1728
|
-
* package has no `TextEncoder` type, because its code operates on each runtime with no global types.
|
|
1729
|
-
*/
|
|
1730
|
-
function utf8(text) {
|
|
1731
|
-
const out = [];
|
|
1732
|
-
for (let i = 0; i < text.length; i++) {
|
|
1733
|
-
let c = text.charCodeAt(i);
|
|
1734
|
-
if (c >= 55296 && c <= 56319 && i + 1 < text.length) {
|
|
1735
|
-
const next = text.charCodeAt(i + 1);
|
|
1736
|
-
if (next >= 56320 && next <= 57343) {
|
|
1737
|
-
c = 65536 + (c - 55296 << 10) + (next - 56320);
|
|
1738
|
-
i++;
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
if (c >= 55296 && c <= 57343) c = 65533;
|
|
1742
|
-
if (c < 128) out.push(c);
|
|
1743
|
-
else if (c < 2048) out.push(192 | c >> 6, 128 | c & 63);
|
|
1744
|
-
else if (c < 65536) out.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
|
|
1745
|
-
else out.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
|
|
1746
|
-
}
|
|
1747
|
-
return Uint8Array.from(out);
|
|
1748
|
-
}
|
|
1749
|
-
/** This function gives the SHA-256 of the UTF-8 bytes of a string, as 64 lowercase hex characters. */
|
|
1750
|
-
function sha256Hex(text) {
|
|
1751
|
-
const bytes = utf8(text);
|
|
1752
|
-
const length = bytes.length;
|
|
1753
|
-
const padded = new Uint8Array(((length + 9 + 63) / 64 | 0) * 64);
|
|
1754
|
-
padded.set(bytes);
|
|
1755
|
-
padded[length] = 128;
|
|
1756
|
-
const view = new DataView(padded.buffer);
|
|
1757
|
-
const bits = length * 8;
|
|
1758
|
-
view.setUint32(padded.length - 8, Math.floor(bits / 4294967296));
|
|
1759
|
-
view.setUint32(padded.length - 4, bits >>> 0);
|
|
1760
|
-
const hash = [...H0];
|
|
1761
|
-
const w = /* @__PURE__ */ new Uint32Array(64);
|
|
1762
|
-
for (let block = 0; block < padded.length; block += 64) {
|
|
1763
|
-
for (let t = 0; t < 16; t++) w[t] = view.getUint32(block + t * 4);
|
|
1764
|
-
for (let t = 16; t < 64; t++) {
|
|
1765
|
-
const a = w[t - 15];
|
|
1766
|
-
const b = w[t - 2];
|
|
1767
|
-
const s0 = rotr(a, 7) ^ rotr(a, 18) ^ a >>> 3;
|
|
1768
|
-
const s1 = rotr(b, 17) ^ rotr(b, 19) ^ b >>> 10;
|
|
1769
|
-
w[t] = w[t - 16] + s0 + w[t - 7] + s1 | 0;
|
|
1770
|
-
}
|
|
1771
|
-
let [a, b, c, d, e, f, g, h] = hash;
|
|
1772
|
-
for (let t = 0; t < 64; t++) {
|
|
1773
|
-
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
1774
|
-
const ch = e & f ^ ~e & g;
|
|
1775
|
-
const t1 = h + s1 + ch + K[t] + w[t] | 0;
|
|
1776
|
-
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) | 0;
|
|
1777
|
-
h = g;
|
|
1778
|
-
g = f;
|
|
1779
|
-
f = e;
|
|
1780
|
-
e = d + t1 | 0;
|
|
1781
|
-
d = c;
|
|
1782
|
-
c = b;
|
|
1783
|
-
b = a;
|
|
1784
|
-
a = t1 + t2 | 0;
|
|
1785
|
-
}
|
|
1786
|
-
hash[0] = hash[0] + a | 0;
|
|
1787
|
-
hash[1] = hash[1] + b | 0;
|
|
1788
|
-
hash[2] = hash[2] + c | 0;
|
|
1789
|
-
hash[3] = hash[3] + d | 0;
|
|
1790
|
-
hash[4] = hash[4] + e | 0;
|
|
1791
|
-
hash[5] = hash[5] + f | 0;
|
|
1792
|
-
hash[6] = hash[6] + g | 0;
|
|
1793
|
-
hash[7] = hash[7] + h | 0;
|
|
1794
|
-
}
|
|
1795
|
-
return hash.map((x) => (x >>> 0).toString(16).padStart(8, "0")).join("");
|
|
1796
|
-
}
|
|
1797
|
-
// src/layout-input.ts
|
|
1798
|
-
/** This function gives the `fnv1a32` hash of the UTF-8 bytes of a string, as `docs/FORMAT.md` gives it. */
|
|
1799
|
-
function fnv1a32(text) {
|
|
1800
|
-
let h = 2166136261;
|
|
1801
|
-
for (const b of utf8(text)) h = Math.imul(h ^ b, 16777619) >>> 0;
|
|
1802
|
-
return h;
|
|
1803
|
-
}
|
|
1804
|
-
/** This function rounds the numbers of a file to 3 decimals. A half goes up, and the value `-0` becomes 0. */
|
|
1805
|
-
function round3(x) {
|
|
1806
|
-
const v = Math.round(x * 1e3) / 1e3;
|
|
1807
|
-
return Object.is(v, -0) ? 0 : v;
|
|
1808
|
-
}
|
|
1809
|
-
/**
|
|
1810
|
-
* This function gives the canonical form of a value: sorted keys, no whitespace and the numbers of `JSON.stringify`
|
|
1811
|
-
* after `round3`. A key with the value `undefined` is not in the form.
|
|
1812
|
-
*/
|
|
1813
|
-
function canonicalJson(value) {
|
|
1814
|
-
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
1815
|
-
if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
1816
|
-
return JSON.stringify(typeof value === "number" ? round3(value) : value);
|
|
1817
|
-
}
|
|
1818
|
-
/** This function gives the hash of an input file: the first 16 hex characters of the SHA-256 of its canonical form without `hash`. */
|
|
1819
|
-
function hashInput(file) {
|
|
1820
|
-
const rest = { ...file };
|
|
1821
|
-
delete rest.hash;
|
|
1822
|
-
return sha256Hex(canonicalJson(rest)).slice(0, 16);
|
|
1823
|
-
}
|
|
1824
|
-
/** 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`. */
|
|
1825
|
-
function countCell(count) {
|
|
1826
|
-
if (count <= 3) return "1";
|
|
1827
|
-
if (count <= 31) return "10";
|
|
1828
|
-
if (count <= 316) return "100";
|
|
1829
|
-
if (count <= 3162) return "1k";
|
|
1830
|
-
return "10k";
|
|
1831
|
-
}
|
|
1832
|
-
/** This function gives the packing input of a file. The boxes become the items, and the edges go. */
|
|
1833
|
-
function toPackInput(file) {
|
|
1834
|
-
const input = {
|
|
1835
|
-
container: file.container,
|
|
1836
|
-
items: file.boxes
|
|
1837
|
-
};
|
|
1838
|
-
if (file.obstacles !== void 0) input.obstacles = file.obstacles;
|
|
1839
|
-
if (file.gap !== void 0) input.gap = file.gap;
|
|
1840
|
-
return input;
|
|
1841
|
-
}
|
|
1842
2131
|
// src/generators.ts
|
|
1843
2132
|
/** The largest count of boxes that a generator makes. */
|
|
1844
2133
|
const MAX_COUNT = 1e5;
|
|
@@ -2751,6 +3040,6 @@ function corpus(name, specs) {
|
|
|
2751
3040
|
inputs
|
|
2752
3041
|
};
|
|
2753
3042
|
}
|
|
2754
|
-
export { DEFAULT_TOLERANCE, EPS, 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, logSweep, lowestWindow, marginOf, mulberry32, pack, packFree, packRows, packShelf, packSkyline, packWith, packingMetrics, push, pushCascade, rectGrid, rectScattered, round3, rows, shelf, skyline, strategies, strategy, supportsCell, toPackInput, toRects, turnItem, turnPoint, turnPort, turnRegion, unsupportedOf, wordCloud };
|
|
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 };
|
|
2755
3044
|
|
|
2756
3045
|
//# sourceMappingURL=index.js.map
|