@mark1russell7/packing 0.1.4 → 0.1.5
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 +105 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +556 -278
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +18 -0
- package/src/measures.ts +363 -0
- package/src/metrics.ts +8 -20
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`.
|
|
756
1173
|
*/
|
|
757
|
-
function
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
+
* ```
|
|
1209
|
+
*/
|
|
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
|
/**
|
|
@@ -1642,203 +2117,6 @@ function checkHard(input, output, tolerance = DEFAULT_TOLERANCE) {
|
|
|
1642
2117
|
}
|
|
1643
2118
|
return breaks;
|
|
1644
2119
|
}
|
|
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
2120
|
// src/generators.ts
|
|
1843
2121
|
/** The largest count of boxes that a generator makes. */
|
|
1844
2122
|
const MAX_COUNT = 1e5;
|
|
@@ -2751,6 +3029,6 @@ function corpus(name, specs) {
|
|
|
2751
3029
|
inputs
|
|
2752
3030
|
};
|
|
2753
3031
|
}
|
|
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 };
|
|
3032
|
+
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
3033
|
|
|
2756
3034
|
//# sourceMappingURL=index.js.map
|