@mark1russell7/layout-contract 0.2.3 → 0.2.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/README.md CHANGED
@@ -16,9 +16,11 @@ The package has no runtime dependencies.
16
16
  | The measures | `MeasureSpec`, `Direction`, `JointMeasureId`, `JOINT_MEASURES`, `JOINT_MOVED_MIN`, `Verdict`, `compareMeasure`, `minEffectOf` |
17
17
  | The edges step | `edgeStep`, `EdgeShape`, `EdgeStepParams`, `EDGE_DENSITY`, `EDGE_STEP_VERSION` |
18
18
  | The random numbers | `mulberry32`, `fnv1a32`, `stepRandom`, `randomInt` |
19
+ | The hashes | `sha256Hex`, `canonicalJson`, `documentHash`, `outputHash`, `HashOptions` |
20
+ | The promotion guards | `outputGroups`, `OutputGroup`, `exercised`, `ExerciseEvidence` |
19
21
  | The turn | `turnBox`, `turnPort`, `turnMargin`, `turnRegion`, `turnSide`, `turnPoint` |
20
22
  | The tolerances | `EPS`, `RECORD_EPS`, `Tolerance`, `DEFAULT_TOLERANCE` |
21
- | The hard rules | `countOverlaps`, `countObstacleHits`, `regionHitsRect`, `countOutside`, `countPortsOffBorder`, `packHard`, `PackHard`, `placedRect`, `checkInput` |
23
+ | The hard rules | `countOverlaps`, `countObstacleHits`, `regionHitsRect`, `rectInRegion`, `countOutside`, `ContainerTest`, `countPortsOffBorder`, `packHard`, `PackHard`, `placedRect`, `checkInput` |
22
24
 
23
25
  ## The rules
24
26
 
@@ -27,6 +29,9 @@ The package has no runtime dependencies.
27
29
  - **The gap.** The gap is the free space between two boxes with their margins. Routes use it. Two boxes are `mA + gap + mB` apart.
28
30
  - **The ports.** A port can have a side. Without a side, the router picks the side. A fixed port has `x` and `y` on the border of the box, relative to its top-left corner.
29
31
  - **The hard rules of a pack.** `no-overlap` counts the pairs of boxes and the pairs of a box and an obstacle. A locked box without a placement, or with a move of more than `RECORD_EPS`, breaks `locked-kept`. `checkInput` refuses a locked box without a position. Another box without a placement and not in `overflow` breaks `complete`.
32
+ - **The container rule.** `in-container` counts each box that goes out of the container made smaller by `inset`. The box counts with its margin. By default, a shaped container counts as its bounding box.
33
+ - **The exact container.** Give `{ exactContainer: true }` to `packHard` or `countOutside`. Then a box of a shaped container with the size `fixed` must stay inside the shape. A cut corner, a notch and a hole count. A container with a free side keeps the bounding test. The default changes to the exact test at the next breaking version.
34
+ - **The output hash.** `outputHash` gives the first 16 hex characters of the SHA-256 of the canonical JSON of an output. Each number goes to the grid of `RECORD_EPS` first.
30
35
  - **The tolerances.** `EPS` (`1e-6`) is for the float compares in an engine. `RECORD_EPS` (`1e-3`) is for the compares on a written file. A record writes the tolerance of each hard rule in its field `tolerance`. The defaults are `overlap` `0.001` and `through` `0.5`.
31
36
 
32
37
  ## An example
@@ -46,4 +51,4 @@ const placed = applyPack(input, output);
46
51
  const hard = packHard(input, output);
47
52
  ```
48
53
 
49
- The formats are in `docs/FORMAT.md` of `packing` and `docs/LAYOUT-RECORDS.md` of `highways`. The joint measures are in `docs/JOINT-MEASURES.md` of this repository, and the joint corpus is in `docs/JOINT-CORPUS.md`.
54
+ The formats are in `docs/FORMAT.md` of `packing` and `docs/LAYOUT-RECORDS.md` of `highways`. The joint measures are in `docs/JOINT-MEASURES.md` of this repository, and the joint corpus is in `docs/JOINT-CORPUS.md`. The promotion guards are in `docs/PROMOTION.md`.
package/build/index.d.ts CHANGED
@@ -324,12 +324,23 @@ export declare function placedRect(box: LayoutBox): Rect | undefined;
324
324
  * A box with a shape counts as its bounding box. That area is larger, thus the count is safe.
325
325
  */
326
326
  export declare function countOverlaps(boxes: readonly LayoutBox[], tolerance?: number): number;
327
+ /** The options of the container test of `countOutside` and `packHard`. */
328
+ export interface ContainerTest {
329
+ /**
330
+ * With `true`, a box of a shaped container with the size `fixed` must stay inside the shape, not only inside its bounding box.
331
+ * The default is `false`. The default changes to `true` at the next breaking version.
332
+ */
333
+ readonly exactContainer?: boolean;
334
+ }
327
335
  /**
328
336
  * This function counts the placed boxes that go out of the container.
329
337
  * Each box with its margin must stay inside the container made smaller by `inset`. Only the fixed sides of the size count. A shaped container counts as its bounding box.
338
+ *
339
+ * With `exactContainer`, a box of a shaped container with the size `fixed` also counts when `rectInRegion` tells that it is not inside the shape.
340
+ * For that test, the box grows by `inset` and becomes smaller by `tolerance` on each side. A shaped container with a free side keeps the bounding test only.
330
341
  * An input without a container gives 0.
331
342
  */
332
- export declare function countOutside(input: LayoutInput, tolerance?: number): number;
343
+ export declare function countOutside(input: LayoutInput, tolerance?: number, options?: ContainerTest): number;
333
344
  /** This function counts the fixed ports that are not on the border of their box, with the epsilon of a written file. A port without a side does not count. */
334
345
  export declare function countPortsOffBorder(boxes: readonly LayoutBox[], eps?: number): number;
335
346
  /** The count of the breaks of each hard rule of a pack. */
@@ -341,6 +352,11 @@ export interface PackHard {
341
352
  }
342
353
  /** This function tells if the inside of `region` and the inside of `rect` have a common point. */
343
354
  export declare function regionHitsRect(region: Region, rect: Rect): boolean;
355
+ /**
356
+ * This function tells if all of `rect` is inside `region`. A point on the border counts as inside. No part of `rect` can overlap a hole.
357
+ * The test of a circle, an ellipse or a polygon accepts an error of `EPS` on the border. A polygon can be concave.
358
+ */
359
+ export declare function rectInRegion(region: Region, rect: Rect): boolean;
344
360
  /**
345
361
  * This function counts the pairs of a placed box and an obstacle that overlap by more than `tolerance`.
346
362
  * The box counts with its margin. A box with a shape counts as its bounding box.
@@ -353,8 +369,10 @@ export declare function countObstacleHits(boxes: readonly LayoutBox[], obstacles
353
369
  * A locked box without a placement, or with a move of more than `RECORD_EPS`, breaks `locked-kept`.
354
370
  * A locked box without a position in the input is a bad input, not a break. `checkInput` finds it.
355
371
  * Another box without a placement that is not in `overflow` breaks `complete`.
372
+ *
373
+ * `options` goes to `countOutside` for `in-container`.
356
374
  */
357
- export declare function packHard(input: LayoutInput, output: PackOutput, tolerance?: number): PackHard;
375
+ export declare function packHard(input: LayoutInput, output: PackOutput, tolerance?: number, options?: ContainerTest): PackHard;
358
376
  /**
359
377
  * This function gives the problems of an input file. An empty list tells that the file is good.
360
378
  * A problem is a bad input, not a break of a hard rule. Thus an engine refuses the input.
@@ -475,4 +493,67 @@ export declare const EDGE_STEP_VERSION = 1;
475
493
  * The rules of each shape are in `docs/JOINT-CORPUS.md`.
476
494
  */
477
495
  export declare function edgeStep(input: LayoutInput, params: EdgeStepParams, seed?: number): LayoutInput;
496
+ /**
497
+ * This function gives the SHA-256 digest of the UTF-8 bytes of a string, as 64 lowercase hex characters.
498
+ * It uses no API of Node or of the browser.
499
+ */
500
+ export declare function sha256Hex(text: string): string;
501
+ /**
502
+ * This function gives the canonical form of a value: sorted keys and no whitespace, with the numbers of `JSON.stringify`.
503
+ * A key with the value `undefined` is not in the form. An `undefined` in an array becomes `null`.
504
+ */
505
+ export declare function canonicalJson(value: unknown): string;
506
+ /** The options of `outputHash` and `documentHash`. */
507
+ export interface HashOptions {
508
+ /**
509
+ * The step of the grid of the numbers. The default is `RECORD_EPS`, so a number keeps 3 decimals.
510
+ * The value `0` keeps the exact numbers. A negative value or a value that is not finite causes a `RangeError`.
511
+ */
512
+ readonly grid?: number;
513
+ }
514
+ /**
515
+ * This function gives the hash of an output: the first 16 hex characters of the SHA-256 of its canonical form.
516
+ * Before the canonical form, each number goes to the nearest point of the grid, and `-0` becomes `0`.
517
+ * With the default grid, the rounding is `round3` of `packing`: `Math.round(x * 1000) / 1000`.
518
+ * The function throws a `RangeError` for `NaN` and for an infinite number.
519
+ *
520
+ * The grid can split two outputs. Two numbers near a rounding boundary can go to two different points.
521
+ * The grid can also merge two outputs whose numbers go to the same points. This merge is intentional: it removes the noise of floating-point numbers.
522
+ */
523
+ export declare function outputHash(value: unknown, options?: HashOptions): string;
524
+ /**
525
+ * This function gives the hash of a document. It uses the rule of `outputHash`, but the form does not include the field `hash` of the document.
526
+ * With the default grid, it is the input hash of `docs/FORMAT.md` of `packing`.
527
+ */
528
+ export declare function documentHash(doc: object, options?: HashOptions): string;
529
+ /** One output group: the candidates with the same output hash on each decision input. */
530
+ export interface OutputGroup {
531
+ /** The id of the group: its first member. */
532
+ readonly id: string;
533
+ /** The ids of the candidates of the group, in sorted order. */
534
+ readonly members: readonly string[];
535
+ }
536
+ /**
537
+ * This function gives the output groups of the candidates. The key of `hashes` is the id of a candidate.
538
+ * Its value is the list of the output hashes of the candidate, in the order of the decision inputs.
539
+ * Candidates with equal lists make one group. The groups are in the order of their ids.
540
+ * The function throws a `RangeError` when two lists have different lengths.
541
+ */
542
+ export declare function outputGroups(hashes: Readonly<Record<string, readonly string[]>>): OutputGroup[];
543
+ /** The evidence that a candidate is exercised on its decision inputs. */
544
+ export interface ExerciseEvidence {
545
+ /** The move counts of the candidate, by name. A record keeps each one as the trace measure `move.<name>`. */
546
+ readonly moves?: Readonly<Record<string, number>>;
547
+ /** The output hashes of the candidate, in the order of the decision inputs. */
548
+ readonly hashes?: readonly string[];
549
+ /** The output hashes of the base of the candidate, in the same order. */
550
+ readonly baseHashes?: readonly string[];
551
+ }
552
+ /**
553
+ * This function tells if a candidate is exercised. It gives `true` when a move count is more than 0.
554
+ * It also gives `true` when `hashes` and `baseHashes` are different at one place or more.
555
+ * Without evidence, it gives `false`.
556
+ * The function throws a `RangeError` when only one of the two lists is given, or when their lengths are different.
557
+ */
558
+ export declare function exercised(evidence: ExerciseEvidence): boolean;
478
559
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/geometry.ts","../src/tolerance.ts","../src/input.ts","../src/output.ts","../src/record.ts","../src/rules.ts","../src/turn.ts","../src/measures.ts","../src/strategy.ts","../src/random.ts","../src/edges.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/geometry.ts","../src/tolerance.ts","../src/input.ts","../src/output.ts","../src/record.ts","../src/rules.ts","../src/turn.ts","../src/measures.ts","../src/strategy.ts","../src/random.ts","../src/edges.ts","../src/hash.ts"],"mappings":"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"}
package/build/index.js CHANGED
@@ -307,11 +307,15 @@ function countOverlaps(boxes, tolerance = DEFAULT_TOLERANCE.overlap) {
307
307
  /**
308
308
  * This function counts the placed boxes that go out of the container.
309
309
  * Each box with its margin must stay inside the container made smaller by `inset`. Only the fixed sides of the size count. A shaped container counts as its bounding box.
310
+ *
311
+ * With `exactContainer`, a box of a shaped container with the size `fixed` also counts when `rectInRegion` tells that it is not inside the shape.
312
+ * For that test, the box grows by `inset` and becomes smaller by `tolerance` on each side. A shaped container with a free side keeps the bounding test only.
310
313
  * An input without a container gives 0.
311
314
  */
312
- function countOutside(input, tolerance = DEFAULT_TOLERANCE.overlap) {
315
+ function countOutside(input, tolerance = DEFAULT_TOLERANCE.overlap, options = {}) {
313
316
  const c = input.container;
314
317
  if (c === void 0) return 0;
318
+ const shape = options.exactContainer === true && c.fixed === "size" ? c.shape : void 0;
315
319
  const area = c.shape === void 0 ? {
316
320
  x: 0,
317
321
  y: 0,
@@ -329,6 +333,7 @@ function countOutside(input, tolerance = DEFAULT_TOLERANCE.overlap) {
329
333
  const outX = r.x < area.x + inset - tolerance || r.x + r.w > area.x + area.w - inset + tolerance;
330
334
  const outY = r.y < area.y + inset - tolerance || r.y + r.h > area.y + area.h - inset + tolerance;
331
335
  if (fixW && outX || fixH && outY) count++;
336
+ else if (shape !== void 0 && !rectInRegion(shape, shrink(grow(r, inset), tolerance))) count++;
332
337
  }
333
338
  return count;
334
339
  }
@@ -373,6 +378,72 @@ function regionHitsRect(region, rect) {
373
378
  }
374
379
  }
375
380
  }
381
+ /**
382
+ * This function tells if all of `rect` is inside `region`. A point on the border counts as inside. No part of `rect` can overlap a hole.
383
+ * The test of a circle, an ellipse or a polygon accepts an error of `EPS` on the border. A polygon can be concave.
384
+ */
385
+ function rectInRegion(region, rect) {
386
+ const x1 = rect.x + rect.w;
387
+ const y1 = rect.y + rect.h;
388
+ const corners = [
389
+ [rect.x, rect.y],
390
+ [x1, rect.y],
391
+ [x1, y1],
392
+ [rect.x, y1]
393
+ ];
394
+ switch (region.kind) {
395
+ case "rect": return rect.x >= region.x && rect.y >= region.y && x1 <= region.x + region.w && y1 <= region.y + region.h;
396
+ case "circle": return corners.every(([x, y]) => inEllipse(region.cx, region.cy, region.r, region.r, x, y));
397
+ case "ellipse": return corners.every(([x, y]) => inEllipse(region.cx, region.cy, region.rx, region.ry, x, y));
398
+ case "polygon": {
399
+ const ring = region.points;
400
+ if (ring.length < 3) return false;
401
+ if (![...corners, [rect.x + rect.w / 2, rect.y + rect.h / 2]].every(([x, y]) => onRing(x, y, ring) || pointInRings(x, y, [ring]))) return false;
402
+ const deep = (x, y) => x > rect.x + 1e-6 && x < x1 - 1e-6 && y > rect.y + 1e-6 && y < y1 - 1e-6;
403
+ for (let i = 0; i < ring.length; i++) {
404
+ const a = ring[i];
405
+ const b = ring[(i + 1) % ring.length];
406
+ const m = clipMid(a[0], a[1], b[0], b[1], rect);
407
+ if (m !== void 0 && deep(m[0], m[1])) return false;
408
+ }
409
+ return (region.holes ?? []).every((hole) => !regionHitsRect({
410
+ kind: "polygon",
411
+ points: hole
412
+ }, rect));
413
+ }
414
+ }
415
+ }
416
+ /** This function gives the rectangle made smaller by `d` on each side. A side does not become smaller than 0. */
417
+ function shrink(r, d) {
418
+ const dx = Math.min(d, r.w / 2);
419
+ const dy = Math.min(d, r.h / 2);
420
+ return {
421
+ x: r.x + dx,
422
+ y: r.y + dy,
423
+ w: r.w - 2 * dx,
424
+ h: r.h - 2 * dy
425
+ };
426
+ }
427
+ /** This function tells if a point is inside an ellipse or on its border, with an error of `EPS`. */
428
+ function inEllipse(cx, cy, rx, ry, x, y) {
429
+ if (rx <= 0 || ry <= 0) return false;
430
+ const u = (x - cx) / rx;
431
+ const v = (y - cy) / ry;
432
+ return u * u + v * v <= 1.000001;
433
+ }
434
+ /** This function tells if a point is on a ring of a polygon, with an error of `EPS`. */
435
+ function onRing(x, y, ring) {
436
+ for (let i = 0; i < ring.length; i++) {
437
+ const [ax, ay] = ring[i];
438
+ const [bx, by] = ring[(i + 1) % ring.length];
439
+ const dx = bx - ax;
440
+ const dy = by - ay;
441
+ const len2 = dx * dx + dy * dy;
442
+ const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((x - ax) * dx + (y - ay) * dy) / len2));
443
+ if (Math.hypot(x - (ax + t * dx), y - (ay + t * dy)) <= 1e-6) return true;
444
+ }
445
+ return false;
446
+ }
376
447
  /** An ellipse with the radii `rx` and `ry` scales to a unit circle. The rectangle scales with it. */
377
448
  function circleHitsRect(cx, cy, rx, ry, rect) {
378
449
  if (rx <= 0 || ry <= 0) return false;
@@ -442,8 +513,10 @@ function countObstacleHits(boxes, obstacles, tolerance = DEFAULT_TOLERANCE.overl
442
513
  * A locked box without a placement, or with a move of more than `RECORD_EPS`, breaks `locked-kept`.
443
514
  * A locked box without a position in the input is a bad input, not a break. `checkInput` finds it.
444
515
  * Another box without a placement that is not in `overflow` breaks `complete`.
516
+ *
517
+ * `options` goes to `countOutside` for `in-container`.
445
518
  */
446
- function packHard(input, output, tolerance = DEFAULT_TOLERANCE.overlap) {
519
+ function packHard(input, output, tolerance = DEFAULT_TOLERANCE.overlap, options = {}) {
447
520
  const after = applyPack(input, output);
448
521
  const placed = new Set(output.placements.map((p) => p.id));
449
522
  const out = new Set(output.overflow);
@@ -466,7 +539,7 @@ function packHard(input, output, tolerance = DEFAULT_TOLERANCE.overlap) {
466
539
  "in-container": countOutside({
467
540
  ...after,
468
541
  boxes: kept
469
- }, tolerance),
542
+ }, tolerance, options),
470
543
  "locked-kept": lockedMoved,
471
544
  complete: incomplete
472
545
  };
@@ -608,6 +681,19 @@ function compareMeasure(candidate, baseline, spec) {
608
681
  if (!(Math.abs(delta) > minEffectOf(spec, candidate, baseline))) return "tie";
609
682
  return (spec.direction === "lower" ? delta < 0 : delta > 0) ? "better" : "worse";
610
683
  }
684
+ // src/utf8.ts
685
+ /** This function gives the UTF-8 bytes of a string. The package has no runtime dependencies, thus it does not use `TextEncoder`. */
686
+ function utf8(text) {
687
+ const bytes = [];
688
+ for (const ch of text) {
689
+ const c = ch.codePointAt(0);
690
+ if (c < 128) bytes.push(c);
691
+ else if (c < 2048) bytes.push(192 | c >> 6, 128 | c & 63);
692
+ else if (c < 65536) bytes.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
693
+ else bytes.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
694
+ }
695
+ return bytes;
696
+ }
611
697
  // src/random.ts
612
698
  /** This function gives a generator of random numbers in [0, 1). It is `mulberry32`, as in `docs/FORMAT.md` of `packing`. */
613
699
  function mulberry32(seed) {
@@ -625,18 +711,6 @@ function fnv1a32(text) {
625
711
  for (const b of utf8(text)) h = Math.imul(h ^ b, 16777619) >>> 0;
626
712
  return h;
627
713
  }
628
- /** The UTF-8 bytes of a string. The package has no runtime dependencies, thus it does not use `TextEncoder`. */
629
- function utf8(text) {
630
- const bytes = [];
631
- for (const ch of text) {
632
- const c = ch.codePointAt(0);
633
- if (c < 128) bytes.push(c);
634
- else if (c < 2048) bytes.push(192 | c >> 6, 128 | c & 63);
635
- else if (c < 65536) bytes.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
636
- else bytes.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
637
- }
638
- return bytes;
639
- }
640
714
  /** This function gives the generator of one step of a seeded generator: `mulberry32(fnv1a32("<seed>:<step>"))`. */
641
715
  function stepRandom(seed, step) {
642
716
  return mulberry32(fnv1a32(`${seed}:${step}`));
@@ -841,6 +915,230 @@ function edgeStep(input, params, seed = input.seed ?? 0) {
841
915
  steps: [...input.steps ?? [], stamp]
842
916
  };
843
917
  }
844
- export { DEFAULT_TOLERANCE, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, JOINT_MEASURES, JOINT_MOVED_MIN, RECORD_EPS, SIDES, applyPack, boundsOf, checkInput, compareMeasure, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, edgeStep, fnv1a32, grow, marginSides, minEffectOf, mulberry32, overlapOf, packHard, placedRect, randomInt, regionHitsRect, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide };
918
+ // src/hash.ts
919
+ /** The round constants of SHA-256. */
920
+ const K = [
921
+ 1116352408,
922
+ 1899447441,
923
+ 3049323471,
924
+ 3921009573,
925
+ 961987163,
926
+ 1508970993,
927
+ 2453635748,
928
+ 2870763221,
929
+ 3624381080,
930
+ 310598401,
931
+ 607225278,
932
+ 1426881987,
933
+ 1925078388,
934
+ 2162078206,
935
+ 2614888103,
936
+ 3248222580,
937
+ 3835390401,
938
+ 4022224774,
939
+ 264347078,
940
+ 604807628,
941
+ 770255983,
942
+ 1249150122,
943
+ 1555081692,
944
+ 1996064986,
945
+ 2554220882,
946
+ 2821834349,
947
+ 2952996808,
948
+ 3210313671,
949
+ 3336571891,
950
+ 3584528711,
951
+ 113926993,
952
+ 338241895,
953
+ 666307205,
954
+ 773529912,
955
+ 1294757372,
956
+ 1396182291,
957
+ 1695183700,
958
+ 1986661051,
959
+ 2177026350,
960
+ 2456956037,
961
+ 2730485921,
962
+ 2820302411,
963
+ 3259730800,
964
+ 3345764771,
965
+ 3516065817,
966
+ 3600352804,
967
+ 4094571909,
968
+ 275423344,
969
+ 430227734,
970
+ 506948616,
971
+ 659060556,
972
+ 883997877,
973
+ 958139571,
974
+ 1322822218,
975
+ 1537002063,
976
+ 1747873779,
977
+ 1955562222,
978
+ 2024104815,
979
+ 2227730452,
980
+ 2361852424,
981
+ 2428436474,
982
+ 2756734187,
983
+ 3204031479,
984
+ 3329325298
985
+ ];
986
+ /**
987
+ * This function gives the SHA-256 digest of the UTF-8 bytes of a string, as 64 lowercase hex characters.
988
+ * It uses no API of Node or of the browser.
989
+ */
990
+ function sha256Hex(text) {
991
+ const bytes = utf8(text);
992
+ const bits = bytes.length * 8;
993
+ const total = Math.ceil((bytes.length + 9) / 64) * 64;
994
+ const data = new Uint8Array(total);
995
+ data.set(bytes);
996
+ data[bytes.length] = 128;
997
+ const view = new DataView(data.buffer);
998
+ view.setUint32(total - 8, Math.floor(bits / 4294967296));
999
+ view.setUint32(total - 4, bits >>> 0);
1000
+ const h = [
1001
+ 1779033703,
1002
+ 3144134277,
1003
+ 1013904242,
1004
+ 2773480762,
1005
+ 1359893119,
1006
+ 2600822924,
1007
+ 528734635,
1008
+ 1541459225
1009
+ ];
1010
+ const w = /* @__PURE__ */ new Uint32Array(64);
1011
+ const rotr = (x, n) => x >>> n | x << 32 - n;
1012
+ for (let off = 0; off < total; off += 64) {
1013
+ for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + 4 * i);
1014
+ for (let i = 16; i < 64; i++) {
1015
+ const a = w[i - 15];
1016
+ const b = w[i - 2];
1017
+ const s0 = rotr(a, 7) ^ rotr(a, 18) ^ a >>> 3;
1018
+ const s1 = rotr(b, 17) ^ rotr(b, 19) ^ b >>> 10;
1019
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
1020
+ }
1021
+ let [a, b, c, d, e, f, g, k] = h;
1022
+ for (let i = 0; i < 64; i++) {
1023
+ const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
1024
+ const ch = e & f ^ ~e & g;
1025
+ const t1 = k + s1 + ch + K[i] + w[i] >>> 0;
1026
+ const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
1027
+ k = g;
1028
+ g = f;
1029
+ f = e;
1030
+ e = d + t1 >>> 0;
1031
+ d = c;
1032
+ c = b;
1033
+ b = a;
1034
+ a = t1 + t2 >>> 0;
1035
+ }
1036
+ h[0] = h[0] + a >>> 0;
1037
+ h[1] = h[1] + b >>> 0;
1038
+ h[2] = h[2] + c >>> 0;
1039
+ h[3] = h[3] + d >>> 0;
1040
+ h[4] = h[4] + e >>> 0;
1041
+ h[5] = h[5] + f >>> 0;
1042
+ h[6] = h[6] + g >>> 0;
1043
+ h[7] = h[7] + k >>> 0;
1044
+ }
1045
+ return h.map((x) => x.toString(16).padStart(8, "0")).join("");
1046
+ }
1047
+ /**
1048
+ * This function gives the canonical form of a value: sorted keys and no whitespace, with the numbers of `JSON.stringify`.
1049
+ * A key with the value `undefined` is not in the form. An `undefined` in an array becomes `null`.
1050
+ */
1051
+ function canonicalJson(value) {
1052
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
1053
+ if (Array.isArray(value)) return `[${value.map((v) => v === void 0 ? "null" : canonicalJson(v)).join(",")}]`;
1054
+ const o = value;
1055
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
1056
+ }
1057
+ /** This function gives the factor of a grid: `1 / grid`, or `undefined` for the exact numbers. */
1058
+ function gridFactor(options) {
1059
+ const grid = options.grid ?? .001;
1060
+ if (!Number.isFinite(grid) || grid < 0) throw new RangeError(`The grid ${grid} is not a finite number of 0 or more.`);
1061
+ return grid === 0 ? void 0 : 1 / grid;
1062
+ }
1063
+ /**
1064
+ * This function gives a copy of a value with each number on the grid. The value `-0` becomes `0`.
1065
+ * It throws a `RangeError` for a number that is not finite.
1066
+ */
1067
+ function onGrid(value, path, k) {
1068
+ if (typeof value === "number") {
1069
+ if (!Number.isFinite(value)) throw new RangeError(`The value has the number ${value} at ${path}. JSON has no form for it.`);
1070
+ if (k === void 0) return value;
1071
+ const r = Math.round(value * k) / k;
1072
+ if (!Number.isFinite(r)) throw new RangeError(`The number at ${path} is too large for the grid.`);
1073
+ return r === 0 ? 0 : r;
1074
+ }
1075
+ if (value === null || typeof value !== "object") return value;
1076
+ if (Array.isArray(value)) return value.map((v, i) => onGrid(v, `${path}[${i}]`, k));
1077
+ const o = value;
1078
+ const out = {};
1079
+ for (const key of Object.keys(o)) out[key] = onGrid(o[key], `${path}.${key}`, k);
1080
+ return out;
1081
+ }
1082
+ /**
1083
+ * This function gives the hash of an output: the first 16 hex characters of the SHA-256 of its canonical form.
1084
+ * Before the canonical form, each number goes to the nearest point of the grid, and `-0` becomes `0`.
1085
+ * With the default grid, the rounding is `round3` of `packing`: `Math.round(x * 1000) / 1000`.
1086
+ * The function throws a `RangeError` for `NaN` and for an infinite number.
1087
+ *
1088
+ * The grid can split two outputs. Two numbers near a rounding boundary can go to two different points.
1089
+ * The grid can also merge two outputs whose numbers go to the same points. This merge is intentional: it removes the noise of floating-point numbers.
1090
+ */
1091
+ function outputHash(value, options = {}) {
1092
+ return sha256Hex(canonicalJson(onGrid(value, "$", gridFactor(options)))).slice(0, 16);
1093
+ }
1094
+ /**
1095
+ * This function gives the hash of a document. It uses the rule of `outputHash`, but the form does not include the field `hash` of the document.
1096
+ * With the default grid, it is the input hash of `docs/FORMAT.md` of `packing`.
1097
+ */
1098
+ function documentHash(doc, options = {}) {
1099
+ const { hash: _hash, ...rest } = doc;
1100
+ return outputHash(rest, options);
1101
+ }
1102
+ /**
1103
+ * This function gives the output groups of the candidates. The key of `hashes` is the id of a candidate.
1104
+ * Its value is the list of the output hashes of the candidate, in the order of the decision inputs.
1105
+ * Candidates with equal lists make one group. The groups are in the order of their ids.
1106
+ * The function throws a `RangeError` when two lists have different lengths.
1107
+ */
1108
+ function outputGroups(hashes) {
1109
+ const byList = /* @__PURE__ */ new Map();
1110
+ let first;
1111
+ for (const id of Object.keys(hashes)) {
1112
+ const list = hashes[id];
1113
+ if (first === void 0) first = id;
1114
+ else if (list.length !== hashes[first].length) throw new RangeError(`The candidate "${id}" has ${list.length} hashes, but "${first}" has ${hashes[first].length}.`);
1115
+ const key = JSON.stringify(list);
1116
+ const members = byList.get(key);
1117
+ if (members) members.push(id);
1118
+ else byList.set(key, [id]);
1119
+ }
1120
+ return [...byList.values()].map((members) => {
1121
+ const sorted = [...members].sort();
1122
+ return {
1123
+ id: sorted[0],
1124
+ members: sorted
1125
+ };
1126
+ }).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
1127
+ }
1128
+ /**
1129
+ * This function tells if a candidate is exercised. It gives `true` when a move count is more than 0.
1130
+ * It also gives `true` when `hashes` and `baseHashes` are different at one place or more.
1131
+ * Without evidence, it gives `false`.
1132
+ * The function throws a `RangeError` when only one of the two lists is given, or when their lengths are different.
1133
+ */
1134
+ function exercised(evidence) {
1135
+ const { moves, hashes, baseHashes } = evidence;
1136
+ if (hashes === void 0 !== (baseHashes === void 0)) throw new RangeError("The evidence must have both hashes and baseHashes, or neither of them.");
1137
+ if (hashes && baseHashes && hashes.length !== baseHashes.length) throw new RangeError(`The candidate has ${hashes.length} hashes, but its base has ${baseHashes.length}.`);
1138
+ if (moves && Object.values(moves).some((n) => n > 0)) return true;
1139
+ if (hashes && baseHashes) return hashes.some((h, i) => h !== baseHashes[i]);
1140
+ return false;
1141
+ }
1142
+ export { DEFAULT_TOLERANCE, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, JOINT_MEASURES, JOINT_MOVED_MIN, RECORD_EPS, SIDES, applyPack, boundsOf, canonicalJson, checkInput, compareMeasure, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, documentHash, edgeStep, exercised, fnv1a32, grow, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, packHard, placedRect, randomInt, rectInRegion, regionHitsRect, sha256Hex, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide };
845
1143
 
846
1144
  //# sourceMappingURL=index.js.map