@mark1russell7/packing 0.1.1 → 0.1.2
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 +18 -0
- package/build/index.d.ts +54 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +104 -1
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/free.ts +153 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -43,6 +43,24 @@ The sort orders are `height-desc` (the default), `height-asc`, `width-desc`, `ar
|
|
|
43
43
|
|
|
44
44
|
The order `input` keeps the order of your array. Use it for a ranked list. `packRows` then fills no gaps, so the rows read in your order: left to right, and top to bottom. `packSkyline` places the rectangles in your order, but a later rectangle can go higher than an earlier one. For a ranked list with `locked` rectangles, use `packShelf`.
|
|
45
45
|
|
|
46
|
+
## Free width
|
|
47
|
+
|
|
48
|
+
`packFree` searches the width. It tries many widths with one strategy of `pack`, and keeps the width with the lowest objective: the area of the bounding box times `1 + aspectWeight * aspectError`.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { packFree } from "@mark1russell7/packing";
|
|
52
|
+
|
|
53
|
+
const result = packFree(rects, undefined, { strategy: "skyline", aspect: 16 / 9 });
|
|
54
|
+
result.outer.size; // the chosen size
|
|
55
|
+
result.fill; // the area of the rectangles divided by the area of the box
|
|
56
|
+
result.aspectError; // the distance from the target aspect, on a log scale
|
|
57
|
+
result.tried; // each width that the search tried, with its objective
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `aspect` is the target: the width divided by the height. The default is 1, a square.
|
|
61
|
+
- `aspectWeight` sets the weight of the aspect. With 0, only the area counts.
|
|
62
|
+
- The search is deterministic. The same input gives the same result.
|
|
63
|
+
|
|
46
64
|
## Pushes and measurements
|
|
47
65
|
|
|
48
66
|
`pushCascade` moves the rectangles that a placed rectangle overlaps, and the rectangles that those overlap in turn:
|
package/build/index.d.ts
CHANGED
|
@@ -151,6 +151,59 @@ interface ShelfOptions {
|
|
|
151
151
|
* rectangle that does not fit is not packed: its `wasPacked` stays `false`.
|
|
152
152
|
*/
|
|
153
153
|
export declare function packShelf<T>(rects: Rect<T>[], outer: Rect<T>, options?: ShelfOptions): Rect<T>;
|
|
154
|
+
/** The options of `packFree` */
|
|
155
|
+
interface FreeWidthOptions extends PackLayoutOptions {
|
|
156
|
+
/** The target aspect: the width divided by the height. The default is 1, a square. */
|
|
157
|
+
aspect?: number;
|
|
158
|
+
/**
|
|
159
|
+
* The weight of the aspect error in the objective. The objective is the area times `1 + aspectWeight * aspectError`.
|
|
160
|
+
* The default is 1. With 0, only the area counts.
|
|
161
|
+
*/
|
|
162
|
+
aspectWeight?: number;
|
|
163
|
+
/** The number of widths that the first pass tries. The default is 48. */
|
|
164
|
+
samples?: number;
|
|
165
|
+
}
|
|
166
|
+
/** One width that `packFree` tried, and its result */
|
|
167
|
+
interface FreeWidthTry {
|
|
168
|
+
/** The width of the container for this try */
|
|
169
|
+
width: number;
|
|
170
|
+
/** The width of the content: the right edge of the rectangles */
|
|
171
|
+
contentWidth: number;
|
|
172
|
+
/** The height of the content */
|
|
173
|
+
contentHeight: number;
|
|
174
|
+
/** The objective of this try. Lower is better. */
|
|
175
|
+
objective: number;
|
|
176
|
+
}
|
|
177
|
+
/** The result of `packFree` */
|
|
178
|
+
interface FreeWidthResult<T> {
|
|
179
|
+
/** The outer rectangle, with the size of the content */
|
|
180
|
+
outer: Rect<T>;
|
|
181
|
+
/** The area of the bounding box of the content */
|
|
182
|
+
area: number;
|
|
183
|
+
/** The area of the rectangles divided by the area of the bounding box */
|
|
184
|
+
fill: number;
|
|
185
|
+
/** The aspect of the bounding box: the width divided by the height */
|
|
186
|
+
aspect: number;
|
|
187
|
+
/** The distance from the target aspect: the absolute value of `log(aspect) - log(target)` */
|
|
188
|
+
aspectError: number;
|
|
189
|
+
/** The objective of the chosen width. Lower is better. */
|
|
190
|
+
objective: number;
|
|
191
|
+
/** Each width that the search tried, in the order of the search */
|
|
192
|
+
tried: FreeWidthTry[];
|
|
193
|
+
}
|
|
194
|
+
/** This function gives the distance between an aspect and a target aspect on a log scale. */
|
|
195
|
+
export declare function aspectError(width: number, height: number, target?: number): number;
|
|
196
|
+
/**
|
|
197
|
+
* This function packs rectangles with a free width. It tries many widths with the strategy of `options`, and keeps
|
|
198
|
+
* the width with the lowest objective: the area of the bounding box times `1 + aspectWeight * aspectError`.
|
|
199
|
+
*
|
|
200
|
+
* - The first pass tries `samples` widths on a log scale, from the widest rectangle to the width of one row.
|
|
201
|
+
* - The second pass tries the widths between the two neighbours of the best width of the first pass.
|
|
202
|
+
* - The search is deterministic: the same input gives the same widths and the same result.
|
|
203
|
+
* - A `locked` rectangle keeps its position. The width is not less than its right edge.
|
|
204
|
+
* - Each strategy of `pack` works with this function, because it only changes the width.
|
|
205
|
+
*/
|
|
206
|
+
export declare function packFree<T>(rects: Rect<T>[], outer?: Rect<T>, options?: FreeWidthOptions): FreeWidthResult<T>;
|
|
154
207
|
/** The options of `pushCascade`. */
|
|
155
208
|
interface PushOptions {
|
|
156
209
|
/** The space between a shoved rectangle and the rectangle that shoves it. The default is 8. */
|
|
@@ -205,5 +258,5 @@ interface MetricsOptions {
|
|
|
205
258
|
* ```
|
|
206
259
|
*/
|
|
207
260
|
export declare function packingMetrics(rects: readonly Rect[], options?: MetricsOptions): PackingMetrics;
|
|
208
|
-
export type { MetricsOptions, PackLayoutOptions, PackOptions, PackingMetrics, PushOptions, ShelfOptions, SortOrder };
|
|
261
|
+
export type { FreeWidthOptions, FreeWidthResult, FreeWidthTry, MetricsOptions, PackLayoutOptions, PackOptions, PackingMetrics, PushOptions, ShelfOptions, SortOrder };
|
|
209
262
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/vector.ts","../src/rect.ts","../src/order.ts","../src/pack.ts","../src/skyline.ts","../src/shelf.ts","../src/push.ts","../src/metrics.ts"],"mappings":";qBACa;;EAIX,YAAY,YAAO;;MAMf;MACA,EAAE;;MAEF;MACA,EAAE;;EAGN,IAAI,WAAW;;EAMf,MAAM,GAAG;;EAMT,IAAI,OAAO,SAAS;;EAKpB;;EAKA,SAAS;;EAKT,MAAM,KAAK,QAAQ,KAAK,SAAS;;SAQ1B,GAAG,iBAAiB;;SAKpB,IAAI,SAAS,WAAW;;SASxB,IAAI,SAAS,WAAW;;aASpB,YAAY;;;qBCxEZ,KAAK;;;EAKhB,KAAK;;EAEL;;EAEA;;EAEA;;;;;EAKA;;MAGI,YAAY;;MAEZ,QAAQ;;EAGZ;;EAEA;;EAGA,WAAW;;EAOX,YAAY,QAAQ;;EAMpB;;SAGO,OAAO,GAAG,OAAO,KAAK,OAAO,KAAK;;SAKlC,SAAS,GAAG,OAAO,KAAK,OAAO,KAAK;;SAKpC,YAAY,GAAG,OAAO,KAAK;;SAK3B,SAAS,GAAG,OAAO,KAAK;;SAKxB,UAAU,GAAG,OAAO,KAAK;;;;;;KC/DtB;;qBAGC,sBAAsB;;wBAGnB,aAAa,OAAO,aAAa,GAAG,GAAG,KAAK,IAAI,GAAG,KAAK;;UCLvD;;EAEf,YAAY;;;UAIG,0BAA0B;;;;;EAKzC;;;;;;;wBAqEc,SAAS,GAAG,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI,UAAS,cAAmB,KAAK;;;;;;;;;;;wBAiB/E,KAAK,GAAG,OAAO,KAAK,MAAM,QAAO,KAAK,IAAiB,UAAS,oBAAyB,KAAK;;;;;;wBChG9F,aAAa,SAAS,mBAAmB,eAAe;EAAe;EAAW;;;;;;;;;;;;;;wBAiClF,YAAY,GAAG,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI;EAAW,YAAY;IAAmB,KAAK;;UCrC/F;;EAEf,YAAY;;;;;;;;;;;;;;wBAiDE,UAAU,GAAG,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI,UAAS,eAAoB,KAAK;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/vector.ts","../src/rect.ts","../src/order.ts","../src/pack.ts","../src/skyline.ts","../src/shelf.ts","../src/free.ts","../src/push.ts","../src/metrics.ts"],"mappings":";qBACa;;EAIX,YAAY,YAAO;;MAMf;MACA,EAAE;;MAEF;MACA,EAAE;;EAGN,IAAI,WAAW;;EAMf,MAAM,GAAG;;EAMT,IAAI,OAAO,SAAS;;EAKpB;;EAKA,SAAS;;EAKT,MAAM,KAAK,QAAQ,KAAK,SAAS;;SAQ1B,GAAG,iBAAiB;;SAKpB,IAAI,SAAS,WAAW;;SASxB,IAAI,SAAS,WAAW;;aASpB,YAAY;;;qBCxEZ,KAAK;;;EAKhB,KAAK;;EAEL;;EAEA;;EAEA;;;;;EAKA;;MAGI,YAAY;;MAEZ,QAAQ;;EAGZ;;EAEA;;EAGA,WAAW;;EAOX,YAAY,QAAQ;;EAMpB;;SAGO,OAAO,GAAG,OAAO,KAAK,OAAO,KAAK;;SAKlC,SAAS,GAAG,OAAO,KAAK,OAAO,KAAK;;SAKpC,YAAY,GAAG,OAAO,KAAK;;SAK3B,SAAS,GAAG,OAAO,KAAK;;SAKxB,UAAU,GAAG,OAAO,KAAK;;;;;;KC/DtB;;qBAGC,sBAAsB;;wBAGnB,aAAa,OAAO,aAAa,GAAG,GAAG,KAAK,IAAI,GAAG,KAAK;;UCLvD;;EAEf,YAAY;;;UAIG,0BAA0B;;;;;EAKzC;;;;;;;wBAqEc,SAAS,GAAG,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI,UAAS,cAAmB,KAAK;;;;;;;;;;;wBAiB/E,KAAK,GAAG,OAAO,KAAK,MAAM,QAAO,KAAK,IAAiB,UAAS,oBAAyB,KAAK;;;;;;wBChG9F,aAAa,SAAS,mBAAmB,eAAe;EAAe;EAAW;;;;;;;;;;;;;;wBAiClF,YAAY,GAAG,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI;EAAW,YAAY;IAAmB,KAAK;;UCrC/F;;EAEf,YAAY;;;;;;;;;;;;;;wBAiDE,UAAU,GAAG,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI,UAAS,eAAoB,KAAK;;UCnDhF,yBAAyB;;EAExC;;;;;EAKA;;EAEA;;;UAIe;;EAEf;;EAEA;;EAEA;;EAEA;;;UAIe,gBAAgB;;EAE/B,OAAO,KAAK;;EAEZ;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,OAAO;;;wBAIO,YAAY,eAAe,gBAAgB;;;;;;;;;;;wBAe3C,SAAS,GAAG,OAAO,KAAK,MAAM,QAAO,KAAK,IAAiB,UAAS,mBAAwB,gBAAgB;;UC3D3G;;EAEf;;EAEA;;;;;;;;;;;;;wBAqDc,YAAY,GAAG,QAAQ,KAAK,IAAI,gBAAgB,KAAK,MAAM,UAAS,cAAmB,KAAK;;UCzD3F;;EAEf;;EAEA;IAAQ;IAAe;;;EAEvB;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf;;;;;;;;;;;wBAYc,eAAe,gBAAgB,QAAQ,UAAS,iBAAsB"}
|
package/build/index.js
CHANGED
|
@@ -425,6 +425,109 @@ function pack(rects, outer = new Rect(), options = {}) {
|
|
|
425
425
|
}
|
|
426
426
|
return outer;
|
|
427
427
|
}
|
|
428
|
+
// src/free.ts
|
|
429
|
+
/** This function gives the distance between an aspect and a target aspect on a log scale. */
|
|
430
|
+
function aspectError(width, height, target = 1) {
|
|
431
|
+
if (!(width > 0 && height > 0 && target > 0)) return Infinity;
|
|
432
|
+
return Math.abs(Math.log(width / height) - Math.log(target));
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* This function packs rectangles with a free width. It tries many widths with the strategy of `options`, and keeps
|
|
436
|
+
* the width with the lowest objective: the area of the bounding box times `1 + aspectWeight * aspectError`.
|
|
437
|
+
*
|
|
438
|
+
* - The first pass tries `samples` widths on a log scale, from the widest rectangle to the width of one row.
|
|
439
|
+
* - The second pass tries the widths between the two neighbours of the best width of the first pass.
|
|
440
|
+
* - The search is deterministic: the same input gives the same widths and the same result.
|
|
441
|
+
* - A `locked` rectangle keeps its position. The width is not less than its right edge.
|
|
442
|
+
* - Each strategy of `pack` works with this function, because it only changes the width.
|
|
443
|
+
*/
|
|
444
|
+
function packFree(rects, outer = new Rect(), options = {}) {
|
|
445
|
+
const target = options.aspect ?? 1;
|
|
446
|
+
const weight = options.aspectWeight ?? 1;
|
|
447
|
+
const samples = Math.max(2, Math.floor(options.samples ?? 48));
|
|
448
|
+
const ox = outer.position.x;
|
|
449
|
+
const oy = outer.position.y;
|
|
450
|
+
let itemArea = 0;
|
|
451
|
+
let minWidth = 0;
|
|
452
|
+
let rowWidth = 0;
|
|
453
|
+
for (const r of rects) {
|
|
454
|
+
if (r.locked) {
|
|
455
|
+
minWidth = Math.max(minWidth, r.right() - ox);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (!(r.size.x > 0 && r.size.y > 0)) continue;
|
|
459
|
+
itemArea += r.area();
|
|
460
|
+
minWidth = Math.max(minWidth, r.size.x);
|
|
461
|
+
rowWidth += r.size.x;
|
|
462
|
+
}
|
|
463
|
+
for (const r of rects) if (r.locked) itemArea += r.area();
|
|
464
|
+
const maxWidth = Math.max(minWidth, rowWidth);
|
|
465
|
+
const tried = [];
|
|
466
|
+
const run = (width) => {
|
|
467
|
+
Rect.resetPacked(rects);
|
|
468
|
+
const box = new Rect();
|
|
469
|
+
box.position.setTo(outer.position);
|
|
470
|
+
box.size.set(width, 0);
|
|
471
|
+
box.fixedWidth = true;
|
|
472
|
+
pack(rects, box, options);
|
|
473
|
+
const placed = Rect.packed(rects);
|
|
474
|
+
const contentWidth = Math.max(0, Rect.maxRight(placed) - ox);
|
|
475
|
+
const contentHeight = Math.max(0, Rect.maxBottom(placed) - oy);
|
|
476
|
+
const area = contentWidth * contentHeight;
|
|
477
|
+
const t = {
|
|
478
|
+
width,
|
|
479
|
+
contentWidth,
|
|
480
|
+
contentHeight,
|
|
481
|
+
objective: area > 0 ? area * (1 + weight * aspectError(contentWidth, contentHeight, target)) : 0
|
|
482
|
+
};
|
|
483
|
+
tried.push(t);
|
|
484
|
+
return t;
|
|
485
|
+
};
|
|
486
|
+
const widths = [];
|
|
487
|
+
if (maxWidth > 0) {
|
|
488
|
+
const ratio = maxWidth / minWidth;
|
|
489
|
+
for (let i = 0; i < samples; i++) {
|
|
490
|
+
const w = minWidth * ratio ** (i / (samples - 1));
|
|
491
|
+
const rounded = i === samples - 1 ? maxWidth : Math.max(minWidth, Math.round(w));
|
|
492
|
+
if (widths.at(-1) !== rounded) widths.push(rounded);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
let best = void 0;
|
|
496
|
+
let bestIndex = 0;
|
|
497
|
+
for (const [i, w] of widths.entries()) {
|
|
498
|
+
const t = run(w);
|
|
499
|
+
if (best === void 0 || t.objective < best.objective) {
|
|
500
|
+
best = t;
|
|
501
|
+
bestIndex = i;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (widths.length > 2) {
|
|
505
|
+
const low = widths[Math.max(0, bestIndex - 1)];
|
|
506
|
+
const high = widths[Math.min(widths.length - 1, bestIndex + 1)];
|
|
507
|
+
const steps = Math.min(Math.floor(samples / 2), Math.max(0, Math.round(high - low) - 1));
|
|
508
|
+
for (let i = 1; i <= steps; i++) {
|
|
509
|
+
const w = Math.round(low + (high - low) * i / (steps + 1));
|
|
510
|
+
if (widths.includes(w)) continue;
|
|
511
|
+
const t = run(w);
|
|
512
|
+
if (best === void 0 || t.objective < best.objective) best = t;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const chosen = best === void 0 ? void 0 : run(best.width);
|
|
516
|
+
tried.pop();
|
|
517
|
+
const w = chosen?.contentWidth ?? 0;
|
|
518
|
+
const h = chosen?.contentHeight ?? 0;
|
|
519
|
+
if (!outer.fixedSize) outer.size.set(w, h);
|
|
520
|
+
const area = w * h;
|
|
521
|
+
return {
|
|
522
|
+
outer,
|
|
523
|
+
area,
|
|
524
|
+
fill: area > 0 ? itemArea / area : 0,
|
|
525
|
+
aspect: h > 0 ? w / h : 0,
|
|
526
|
+
aspectError: aspectError(w, h, target),
|
|
527
|
+
objective: chosen?.objective ?? 0,
|
|
528
|
+
tried
|
|
529
|
+
};
|
|
530
|
+
}
|
|
428
531
|
// src/push.ts
|
|
429
532
|
/** This function gives the overlap of two rectangles on each axis. A value of 0 or less is no overlap. */
|
|
430
533
|
function overlapOf(a, b) {
|
|
@@ -555,6 +658,6 @@ function packingMetrics(rects, options = {}) {
|
|
|
555
658
|
widestOverMedian: median > 0 ? widest / median : 0
|
|
556
659
|
};
|
|
557
660
|
}
|
|
558
|
-
export { Rect, SORT_ORDERS, Vector, compareRects, lowestWindow, pack, packRows, packShelf, packSkyline, packingMetrics, pushCascade };
|
|
661
|
+
export { Rect, SORT_ORDERS, Vector, aspectError, compareRects, lowestWindow, pack, packFree, packRows, packShelf, packSkyline, packingMetrics, pushCascade };
|
|
559
662
|
|
|
560
663
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/vector.ts","../src/rect.ts","../src/order.ts","../src/shelf.ts","../src/skyline.ts","../src/pack.ts","../src/push.ts","../src/metrics.ts"],"sourcesContent":["/** A mutable two-dimensional vector. */\nexport class Vector {\n #x: number;\n #y: number;\n\n constructor(x = 0, y = 0) {\n this.#x = x;\n this.#y = y;\n }\n\n /** The first component: a width or a left position. */\n get x(): number { return this.#x; }\n set x(v: number) { this.#x = v; }\n /** The second component: a height or a top position. */\n get y(): number { return this.#y; }\n set y(v: number) { this.#y = v; }\n\n /** This method sets both components. */\n set(x: number, y: number): void {\n this.#x = x;\n this.#y = y;\n }\n\n /** This method copies the components of another vector. */\n setTo(v: Vector): void {\n this.#x = v.x;\n this.#y = v.y;\n }\n\n /** This method gives a new vector: the sum of this vector and another. */\n add(other: Vector): Vector {\n return new Vector(this.#x + other.x, this.#y + other.y);\n }\n\n /** This method gives the product of the components: the area of a size. */\n area(): number {\n return this.#x * this.#y;\n }\n\n /** This method gives a new vector with the same components. */\n clone(): Vector {\n return new Vector(this.#x, this.#y);\n }\n\n /** This method gives a new vector with each component between the components of `min` and `max`. */\n clamp(min: Vector, max: Vector): Vector {\n return new Vector(\n Math.max(min.x, Math.min(max.x, this.#x)),\n Math.max(min.y, Math.min(max.y, this.#y)),\n );\n }\n\n /** This function gives a vector with the same value in both components. */\n static of(scalar: number): Vector {\n return new Vector(scalar, scalar);\n }\n\n /** This function gives the smallest value of each component, or a zero vector for an empty list. */\n static min(vectors: Vector[]): Vector {\n if (!vectors.length) return new Vector();\n return new Vector(\n Math.min(...vectors.map((v) => v.x)),\n Math.min(...vectors.map((v) => v.y)),\n );\n }\n\n /** This function gives the largest value of each component, or a zero vector for an empty list. */\n static max(vectors: Vector[]): Vector {\n if (!vectors.length) return new Vector();\n return new Vector(\n Math.max(...vectors.map((v) => v.x)),\n Math.max(...vectors.map((v) => v.y)),\n );\n }\n\n /** This getter gives a new infinite vector each time. A shared value is not safe, because a vector is mutable. */\n static get Infinity(): Vector {\n return new Vector(Infinity, Infinity);\n }\n}\n","import { Vector } from \"./vector.ts\";\n\n/** A rectangle of the packer. `id` identifies the item that it measures. The packer sets `position` and `wasPacked`. */\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- the parameter types the id of the caller\nexport class Rect<T = unknown> {\n readonly #position = new Vector();\n readonly #size = new Vector();\n\n /** The identity of the item that the rectangle measures. */\n id?: T;\n /** The packer sets this field to `true` when it places the rectangle. */\n wasPacked = false;\n /** For an outer rectangle: the packer keeps its width. */\n fixedWidth = false;\n /** For an outer rectangle: the packer keeps its width and its height, and it skips a rectangle that does not fit. */\n fixedSize = false;\n /**\n * The packer keeps the position of a locked rectangle. `packSkyline` packs the other rectangles around it.\n * `packRows` only keeps its position, and other rectangles can overlap it.\n */\n locked = false;\n\n /** The top left corner. */\n get position(): Vector { return this.#position; }\n /** The width and the height. */\n get size(): Vector { return this.#size; }\n\n /** This method gives the right edge. */\n right(): number { return this.#position.x + this.#size.x; }\n /** This method gives the bottom edge. */\n bottom(): number { return this.#position.y + this.#size.y; }\n\n /** This method moves the left edge to the right by an amount, and the right edge stays. */\n shrinkLeft(amount: number): this {\n this.#position.x += amount;\n this.#size.x -= amount;\n return this;\n }\n\n /** This method moves the rectangle by an offset. */\n translateBy(offset: Vector): this {\n this.#position.set(this.#position.x + offset.x, this.#position.y + offset.y);\n return this;\n }\n\n /** This method gives the area of the size. */\n area(): number { return this.#size.area(); }\n\n /** This function gives the rectangles that the packer placed. */\n static packed<U>(rects: Rect<U>[]): Rect<U>[] {\n return rects.filter((r) => r.wasPacked);\n }\n\n /** This function gives the rectangles that the packer did not place. */\n static unpacked<U>(rects: Rect<U>[]): Rect<U>[] {\n return rects.filter((r) => !r.wasPacked);\n }\n\n /** This function marks each rectangle as not placed. A locked rectangle stays placed. */\n static resetPacked<U>(rects: Rect<U>[]): void {\n for (const r of rects) r.wasPacked = r.locked;\n }\n\n /** This function gives the largest right edge, or 0 for an empty list. */\n static maxRight<U>(rects: Rect<U>[]): number {\n return rects.reduce((acc, r) => Math.max(acc, r.right()), 0);\n }\n\n /** This function gives the largest bottom edge, or 0 for an empty list. */\n static maxBottom<U>(rects: Rect<U>[]): number {\n return rects.reduce((acc, r) => Math.max(acc, r.bottom()), 0);\n }\n}\n","import type { Rect } from \"./rect.ts\";\n\n/**\n * An order of the rectangles before the packing. The order changes the layout much. `input` keeps the order of\n * the array of the caller: the row packing then fills no gaps, so its rows read in that order.\n */\nexport type SortOrder = \"height-desc\" | \"height-asc\" | \"width-desc\" | \"area-desc\" | \"input\";\n\n/** This constant lists each sort order. The first one is the default. */\nexport const SORT_ORDERS: readonly SortOrder[] = [\"height-desc\", \"height-asc\", \"width-desc\", \"area-desc\", \"input\"];\n\n/** This function gives the comparator of a sort order. The sort is stable, so `input` keeps the order. */\nexport function compareRects(order: SortOrder): <T>(a: Rect<T>, b: Rect<T>) => number {\n switch (order) {\n case \"height-desc\":\n return (a, b) => b.size.y - a.size.y;\n case \"height-asc\":\n return (a, b) => a.size.y - b.size.y;\n case \"width-desc\":\n return (a, b) => b.size.x - a.size.x;\n case \"area-desc\":\n return (a, b) => b.area() - a.area();\n case \"input\":\n return () => 0;\n }\n}\n","import { compareRects, type SortOrder } from \"./order.ts\";\nimport type { Rect } from \"./rect.ts\";\n\n/** The options of `packShelf` */\nexport interface ShelfOptions {\n /** The order of the rectangles before the packing. The default is `height-desc`. */\n sortOrder?: SortOrder;\n}\n\n/** An obstacle in the coordinates of the outer rectangle */\ninterface Box {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\n/**\n * This function gives the first x from `start` where a rectangle of width `w` crosses no obstacle in the band from\n * `top` to `bottom`.\n */\nfunction firstFreeX(obstacles: readonly Box[], start: number, w: number, top: number, bottom: number): number {\n let x = start;\n let moved = true;\n while (moved) {\n moved = false;\n for (const o of obstacles) {\n if (o.top < bottom && top < o.bottom && o.left < x + w && x < o.right) {\n x = o.right;\n moved = true;\n }\n }\n }\n return x;\n}\n\n/** This function gives the lowest bottom edge of the obstacles in the band from `top` to `bottom`, under `top`. */\nfunction nextTop(obstacles: readonly Box[], top: number, bottom: number): number {\n let next = Infinity;\n for (const o of obstacles) if (o.top < bottom && top < o.bottom && o.bottom > top) next = Math.min(next, o.bottom);\n return next;\n}\n\n/**\n * This function packs rectangles into shelves: rows that it fills left to right in the sort order, and does not fill\n * again. It goes around each `locked` rectangle. It sets the position of each rectangle and gives the outer rectangle.\n *\n * - With the sort order `input`, the rows read in the order of the array: left to right, and top to bottom.\n * - With `height-desc` (the default), it is the classic Next-Fit Decreasing Height packing.\n * - A rectangle goes to the first x in the row where it crosses no locked rectangle. If no such x is in the width,\n * a new row starts under the current row.\n * - A rectangle wider than the outer rectangle goes on its own row, at the left edge, and overflows.\n * - It does not change the width. Without `fixedSize`, the height grows to the content. With `fixedSize`, a\n * rectangle that does not fit is not packed: its `wasPacked` stays `false`.\n */\nexport function packShelf<T>(rects: Rect<T>[], outer: Rect<T>, options: ShelfOptions = {}): Rect<T> {\n const width = outer.size.x;\n const obstacles: Box[] = [];\n let contentBottom = 0;\n for (const rect of rects) {\n if (!rect.locked) continue;\n rect.wasPacked = true;\n const left = rect.position.x - outer.position.x;\n const top = rect.position.y - outer.position.y;\n obstacles.push({ left, top, right: left + rect.size.x, bottom: top + rect.size.y });\n contentBottom = Math.max(contentBottom, top + rect.size.y);\n }\n\n let rowTop = 0;\n let rowBottom = 0;\n let cursor = 0;\n let rowEmpty = true;\n\n for (const rect of rects.toSorted(compareRects(options.sortOrder ?? \"height-desc\"))) {\n if (rect.wasPacked) continue;\n const w = rect.size.x;\n const h = rect.size.y;\n if (!(w > 0 && h > 0)) continue;\n const overflows = w > width;\n if (overflows && outer.fixedSize) continue;\n\n // An overflowing rectangle starts a row of its own\n if (overflows && !rowEmpty) {\n rowTop = rowBottom;\n cursor = 0;\n rowEmpty = true;\n }\n\n let x = firstFreeX(obstacles, cursor, w, rowTop, rowTop + h);\n while (!overflows && x + w > width) {\n // Start a new row under the current one, or under the obstacles that block an empty row\n rowTop = rowEmpty ? nextTop(obstacles, rowTop, rowTop + h) : rowBottom;\n rowBottom = rowTop;\n cursor = 0;\n rowEmpty = true;\n x = firstFreeX(obstacles, cursor, w, rowTop, rowTop + h);\n }\n while (overflows && x !== 0) {\n rowTop = nextTop(obstacles, rowTop, rowTop + h);\n rowBottom = rowTop;\n x = firstFreeX(obstacles, 0, w, rowTop, rowTop + h);\n }\n\n if (outer.fixedSize && rowTop + h > outer.size.y) continue;\n\n rect.position.set(x + outer.position.x, rowTop + outer.position.y);\n rect.wasPacked = true;\n rowEmpty = false;\n cursor = x + w;\n rowBottom = Math.max(rowBottom, rowTop + h);\n contentBottom = Math.max(contentBottom, rowTop + h);\n if (overflows) {\n rowTop = rowBottom;\n cursor = 0;\n rowEmpty = true;\n }\n }\n\n if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, contentBottom);\n return outer;\n}\n","import { compareRects, type SortOrder } from \"./order.ts\";\nimport type { Rect } from \"./rect.ts\";\n\n/**\n * This function finds the lowest window of width `rw` on a skyline. For each `x` in `[0, width - rw]`, the height\n * of the window is the largest value of `skyline[x, x + rw)`. The first `x` with the smallest height wins. A\n * monotonic deque makes the search O(width) for one rectangle.\n */\nexport function lowestWindow(skyline: ArrayLike<number>, width: number, rw: number): { x: number; y: number } {\n const deque = new Int32Array(width);\n let head = 0;\n let tail = 0;\n let bestX = 0;\n let bestY = Infinity;\n for (let i = 0; i < width; i++) {\n while (tail > head && skyline[deque[tail - 1]!]! <= skyline[i]!) tail--;\n deque[tail++] = i;\n if (deque[head]! <= i - rw) head++;\n if (i >= rw - 1) {\n const max = skyline[deque[head]!]!;\n if (max < bestY) {\n bestY = max;\n bestX = i - rw + 1;\n }\n }\n }\n return { x: bestX, y: bestY };\n}\n\n/**\n * This function packs rectangles into an outer rectangle with a skyline. It puts each rectangle in the lowest place\n * where it fits, and it does not change the width of the outer rectangle.\n *\n * - A `locked` rectangle keeps its position. The skyline starts at its bottom edge across its width, thus the other\n * rectangles go under it. The space above it stays empty, because a skyline has no holes.\n * - The skyline has a cell for each whole unit of the width. A rectangle that needs more cells than the outer\n * rectangle has goes under all other rectangles, at the left edge.\n * - Without `fixedSize`, the height of the outer rectangle grows to the content. With `fixedSize`, a rectangle that\n * does not fit is not packed: its `wasPacked` stays `false`.\n * - The default sort order is `height-desc`. The order of the array of the caller stays.\n */\nexport function packSkyline<T>(rects: Rect<T>[], outer: Rect<T>, options: { sortOrder?: SortOrder } = {}): Rect<T> {\n // a cell for each whole unit of the width: a rectangle never goes past the right edge\n const width = Math.floor(outer.size.x);\n if (width <= 0) return outer;\n const skyline = new Float64Array(width);\n\n for (const rect of rects) {\n if (!rect.locked) continue;\n rect.wasPacked = true;\n const x0 = Math.max(0, Math.floor(rect.position.x - outer.position.x));\n const x1 = Math.min(width, Math.ceil(rect.position.x - outer.position.x + rect.size.x));\n const bottom = rect.position.y - outer.position.y + rect.size.y;\n for (let i = x0; i < x1; i++) skyline[i] = Math.max(skyline[i]!, bottom);\n if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, bottom);\n }\n\n for (const rect of rects.toSorted(compareRects(options.sortOrder ?? \"height-desc\"))) {\n if (rect.wasPacked) continue;\n const rw = Math.ceil(rect.size.x);\n const rh = rect.size.y;\n if (rw <= 0 || rh <= 0) continue;\n\n let x: number;\n let y: number;\n if (rw > width) {\n if (outer.fixedSize) continue;\n x = 0;\n y = 0;\n for (let i = 0; i < width; i++) if (skyline[i]! > y) y = skyline[i]!;\n } else {\n ({ x, y } = lowestWindow(skyline, width, rw));\n if (outer.fixedSize && y + rh > outer.size.y) continue;\n }\n\n rect.position.set(x + outer.position.x, y + outer.position.y);\n rect.wasPacked = true;\n const top = y + rh;\n for (let i = x; i < Math.min(width, x + rw); i++) skyline[i] = top;\n if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, top);\n }\n return outer;\n}\n","import { compareRects, type SortOrder } from \"./order.ts\";\nimport { Rect } from \"./rect.ts\";\nimport { packShelf } from \"./shelf.ts\";\nimport { packSkyline } from \"./skyline.ts\";\nimport { Vector } from \"./vector.ts\";\n\n/** The options of `pack` and `packRows` */\nexport interface PackOptions {\n /** The order of the rectangles before the packing. The default is `height-desc`. */\n sortOrder?: SortOrder;\n}\n\n/** The options of `pack` */\nexport interface PackLayoutOptions extends PackOptions {\n /**\n * The packing: `rows` (the default), `skyline` or `shelf`. `skyline` and `shelf` pack around locked rectangles.\n * Use `shelf` with the sort order `input` to keep the order of the array around locked rectangles.\n */\n strategy?: \"rows\" | \"skyline\" | \"shelf\";\n}\n\nfunction packInto<T>(rects: Rect<T>[], region: Rect<T>, offset: Vector, order: SortOrder): void {\n const unpacked = Rect.unpacked(rects);\n pack(unpacked, region, { sortOrder: order });\n for (const r of Rect.packed(unpacked)) r.translateBy(offset);\n}\n\nfunction packRow<T>(rects: Rect<T>[], outer: Rect<T>, order: SortOrder): boolean {\n const row: Rect<T>[] = [];\n // toSorted gives a sorted copy, thus the order of the array of the caller stays\n const byHeight = rects.toSorted(compareRects(order));\n // A gap fill puts a later rect before an earlier one, thus the order `input` fills no gaps\n const fillGaps = order !== \"input\";\n\n const rowRect = new Rect<T>();\n rowRect.size.set(outer.size.x, 0);\n\n for (const rect of byHeight) {\n if (rect.wasPacked) continue;\n\n if (rect.size.x > rowRect.size.x) {\n if (outer.fixedSize && rect.size.x > outer.size.x) continue;\n\n const outerLeft = new Rect<T>();\n outerLeft.position.setTo(rowRect.position);\n outerLeft.size.set(outer.size.x - rowRect.position.x, rowRect.size.y);\n outerLeft.fixedSize = true;\n if (fillGaps) packInto(rects, outerLeft, outer.position, order);\n\n rowRect.position.set(0, rowRect.bottom());\n rowRect.size.set(outer.size.x, 0);\n row.length = 0;\n }\n\n if (rowRect.position.y + rect.size.y > outer.size.y) {\n if (outer.fixedSize) continue;\n outer.size.y += rect.size.y;\n if (!outer.fixedWidth && !outer.size.x) outer.size.x = rect.size.x;\n Rect.resetPacked(rects);\n return false;\n }\n\n row.push(rect);\n rect.position.setTo(rowRect.position.add(outer.position));\n rowRect.shrinkLeft(rect.size.x);\n rect.wasPacked = true;\n\n if (rect.size.y > rowRect.size.y) rowRect.size.y = rect.size.y;\n\n if (fillGaps && row.length > 1) {\n const rectInRow = row[row.length - 1]!;\n const outerLeft = new Rect<T>();\n outerLeft.position.set(rectInRow.position.x, rectInRow.bottom());\n outerLeft.size.set(rectInRow.size.x, rowRect.size.y - rectInRow.size.y);\n outerLeft.fixedWidth = true;\n outerLeft.fixedSize = true;\n packInto(rects, outerLeft, outer.position, order);\n }\n }\n return true;\n}\n\n/**\n * This function packs rectangles into an outer rectangle of a given width with a guillotine row packing. It does\n * not change the width. Without `fixedSize`, the height grows until all rectangles fit. A `locked` rectangle keeps\n * its position, but the rows do not go around it. `pack` sets the width first and then uses this function.\n */\nexport function packRows<T>(rects: Rect<T>[], outer: Rect<T>, options: PackOptions = {}): Rect<T> {\n const order = options.sortOrder ?? \"height-desc\";\n for (const r of rects) if (r.locked) r.wasPacked = true;\n while (!packRow(rects, outer, order));\n return outer;\n}\n\n/**\n * This function packs rectangles into an outer rectangle with a guillotine row packing. It sets the position\n * of each rectangle and the size of the outer rectangle, and it gives the outer rectangle.\n *\n * - Without fixed sizes, the outer rectangle starts with a width of 1.5 times the square root of the total area.\n * This width is not less than the width of the widest rectangle. The height grows until all rectangles fit.\n * - With `fixedWidth`, the width stays. A rectangle wider than that width goes on its own row and overflows.\n * - With `fixedSize`, a rectangle that does not fit is not packed: its `wasPacked` stays `false`.\n * - `options.strategy` selects `packRows` (the default), `packSkyline` or `packShelf` after the width is set.\n */\nexport function pack<T>(rects: Rect<T>[], outer: Rect<T> = new Rect(), options: PackLayoutOptions = {}): Rect<T> {\n let totalArea = 0;\n let widest = 0;\n let minX = rects.length > 0 ? Infinity : 0;\n let minY = rects.length > 0 ? Infinity : 0;\n for (const r of rects) {\n totalArea += r.area();\n widest = Math.max(widest, r.size.x);\n minX = Math.min(minX, r.size.x);\n minY = Math.min(minY, r.size.y);\n }\n\n const idealWidth = outer.fixedWidth\n ? outer.size.x\n : Math.max(widest, Math.min(1.5 * Math.sqrt(totalArea), outer.size.x || Infinity));\n\n const ideal = Vector.of(idealWidth).clamp(new Vector(minX, minY), Vector.Infinity);\n\n // A fixed width stays, also when a rectangle is wider: that rectangle overflows\n if (!outer.fixedSize) outer.size.setTo(outer.fixedWidth ? new Vector(outer.size.x, ideal.y) : ideal);\n if (options.strategy === \"skyline\") packSkyline(rects, outer, options);\n else if (options.strategy === \"shelf\") packShelf(rects, outer, options);\n else packRows(rects, outer, options);\n\n if (!outer.fixedSize) {\n const contentRight = Rect.maxRight(rects);\n if (!outer.size.x) outer.size.x = contentRight;\n else if (!outer.fixedWidth) outer.size.x = Math.min(contentRight, outer.size.x);\n outer.size.y = Rect.maxBottom(rects);\n }\n\n return outer;\n}\n","import type { Rect } from \"./rect.ts\";\n\n/** The options of `pushCascade`. */\nexport interface PushOptions {\n /** The space between a shoved rectangle and the rectangle that shoves it. The default is 8. */\n gap?: number;\n /** The most shoves. The default is eight for each rectangle. The cascade stops at this limit. */\n maxShoves?: number;\n}\n\n/** A direction of a shove: an axis and a sign. */\ninterface Direction {\n readonly axis: \"x\" | \"y\";\n readonly sign: 1 | -1;\n}\n\n/** This function gives the overlap of two rectangles on each axis. A value of 0 or less is no overlap. */\nfunction overlapOf(a: Rect, b: Rect): { x: number; y: number } {\n return {\n x: Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x),\n y: Math.min(a.bottom(), b.bottom()) - Math.max(a.position.y, b.position.y),\n };\n}\n\n/**\n * This function gives the direction of a first shove. The axis has the smaller overlap. The sign points away from the\n * center of the pusher. When that shove crosses 0, the sign is positive.\n */\nfunction firstDirection(pusher: Rect, r: Rect, gap: number): Direction {\n const overlap = overlapOf(pusher, r);\n if (overlap.x < overlap.y) {\n const back = r.position.x + r.size.x / 2 < pusher.position.x + pusher.size.x / 2 && pusher.position.x - r.size.x - gap >= 0;\n return { axis: \"x\", sign: back ? -1 : 1 };\n }\n const back = r.position.y + r.size.y / 2 < pusher.position.y + pusher.size.y / 2 && pusher.position.y - r.size.y - gap >= 0;\n return { axis: \"y\", sign: back ? -1 : 1 };\n}\n\n/** This function puts a rectangle past the pusher, along a direction. The rectangle does not go past 0. */\nfunction shove(pusher: Rect, r: Rect, direction: Direction, gap: number): void {\n if (direction.axis === \"x\") {\n const x = direction.sign > 0 ? pusher.right() + gap : pusher.position.x - r.size.x - gap;\n r.position.set(Math.max(0, x), r.position.y);\n return;\n }\n const y = direction.sign > 0 ? pusher.bottom() + gap : pusher.position.y - r.size.y - gap;\n r.position.set(r.position.x, Math.max(0, y));\n}\n\n/**\n * This function moves each rectangle that a placed rectangle overlaps, and the rectangles that those overlap in turn.\n * A shove takes the axis of the smaller overlap, away from the pusher. A shoved rectangle keeps the direction of its first\n * shove when it shoves the next one, so the cascade goes outward. The placed rectangle and each locked rectangle stay.\n * The function moves the rectangles in place and gives the rectangles that moved.\n *\n * @example\n * ```ts\n * const moved = pushCascade(dropped, cards, { gap: 8 });\n * ```\n */\nexport function pushCascade<T>(placed: Rect<T>, rects: readonly Rect<T>[], options: PushOptions = {}): Rect<T>[] {\n const gap = options.gap ?? 8;\n const maxShoves = options.maxShoves ?? rects.length * 8;\n const moved = new Set<Rect<T>>();\n const queue: { pusher: Rect<T>; direction?: Direction }[] = [{ pusher: placed }];\n let shoves = 0;\n while (shoves < maxShoves) {\n const step = queue.shift();\n if (!step) break;\n const { pusher, direction } = step;\n for (const r of rects) {\n if (r === pusher || r === placed || r.locked) continue;\n const overlap = overlapOf(pusher, r);\n if (overlap.x <= 0 || overlap.y <= 0) continue;\n const next = direction ?? firstDirection(pusher, r, gap);\n shove(pusher, r, next, gap);\n moved.add(r);\n queue.push({ pusher: r, direction: next });\n if (++shoves >= maxShoves) break;\n }\n }\n return [...moved];\n}\n","import type { Rect } from \"./rect.ts\";\n\n/** The measurements of a packing. */\nexport interface PackingMetrics {\n /** The number of rectangles. */\n count: number;\n /** The size of the hull: the smallest box around the rectangles. */\n hull: { width: number; height: number };\n /** The width of the hull divided by its height. A hull without height gives 0. */\n hullAspect: number;\n /** The number of rectangles in each row, from the top. */\n rows: number[];\n /** The largest right edge of a row less the smallest right edge of a row. A straight right side gives 0. */\n raggedness: number;\n /** The part of the hull that the rectangles cover: their area divided by the area of the hull. */\n utilization: number;\n /** The width of the widest rectangle divided by the median width. */\n widestOverMedian: number;\n}\n\n/** The options of `packingMetrics`. */\nexport interface MetricsOptions {\n /** A rectangle joins a row when its top is this close to the top of the first rectangle of the row. The default is 40. */\n rowTolerance?: number;\n}\n\n/**\n * This function measures a packing. It gives the hull and its aspect, the rows and the raggedness of the right side.\n * It also gives the part of the hull that the rectangles cover, and the widest rectangle against the median width.\n *\n * @example\n * ```ts\n * const { utilization, raggedness } = packingMetrics(Rect.packed(cards));\n * ```\n */\nexport function packingMetrics(rects: readonly Rect[], options: MetricsOptions = {}): PackingMetrics {\n const tolerance = options.rowTolerance ?? 40;\n const boxes = rects.filter((r) => r.size.x > 0 && r.size.y > 0);\n if (boxes.length === 0) return { count: 0, hull: { width: 0, height: 0 }, hullAspect: 0, rows: [], raggedness: 0, utilization: 0, widestOverMedian: 0 };\n\n const left = Math.min(...boxes.map((r) => r.position.x));\n const top = Math.min(...boxes.map((r) => r.position.y));\n const right = Math.max(...boxes.map((r) => r.right()));\n const bottom = Math.max(...boxes.map((r) => r.bottom()));\n const hull = { width: right - left, height: bottom - top };\n\n // The rows: the rectangles by their top, and a row takes each rectangle near the top of its first rectangle\n const rows: { top: number; right: number; count: number }[] = [];\n for (const r of boxes.toSorted((a, b) => a.position.y - b.position.y)) {\n const row = rows.at(-1);\n if (row && Math.abs(r.position.y - row.top) <= tolerance) {\n row.right = Math.max(row.right, r.right());\n row.count += 1;\n } else {\n rows.push({ top: r.position.y, right: r.right(), count: 1 });\n }\n }\n const rights = rows.map((row) => row.right);\n const widths = boxes.map((r) => r.size.x).toSorted((a, b) => a - b);\n const median = widths[Math.floor(widths.length / 2)] ?? 0;\n const widest = widths.at(-1) ?? 0;\n const area = boxes.reduce((sum, r) => sum + r.area(), 0);\n return {\n count: boxes.length,\n hull,\n hullAspect: hull.height > 0 ? hull.width / hull.height : 0,\n rows: rows.map((row) => row.count),\n raggedness: Math.max(...rights) - Math.min(...rights),\n utilization: hull.width * hull.height > 0 ? area / (hull.width * hull.height) : 0,\n widestOverMedian: median > 0 ? widest / median : 0,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/vector.ts","../src/rect.ts","../src/order.ts","../src/shelf.ts","../src/skyline.ts","../src/pack.ts","../src/free.ts","../src/push.ts","../src/metrics.ts"],"sourcesContent":["/** A mutable two-dimensional vector. */\nexport class Vector {\n #x: number;\n #y: number;\n\n constructor(x = 0, y = 0) {\n this.#x = x;\n this.#y = y;\n }\n\n /** The first component: a width or a left position. */\n get x(): number { return this.#x; }\n set x(v: number) { this.#x = v; }\n /** The second component: a height or a top position. */\n get y(): number { return this.#y; }\n set y(v: number) { this.#y = v; }\n\n /** This method sets both components. */\n set(x: number, y: number): void {\n this.#x = x;\n this.#y = y;\n }\n\n /** This method copies the components of another vector. */\n setTo(v: Vector): void {\n this.#x = v.x;\n this.#y = v.y;\n }\n\n /** This method gives a new vector: the sum of this vector and another. */\n add(other: Vector): Vector {\n return new Vector(this.#x + other.x, this.#y + other.y);\n }\n\n /** This method gives the product of the components: the area of a size. */\n area(): number {\n return this.#x * this.#y;\n }\n\n /** This method gives a new vector with the same components. */\n clone(): Vector {\n return new Vector(this.#x, this.#y);\n }\n\n /** This method gives a new vector with each component between the components of `min` and `max`. */\n clamp(min: Vector, max: Vector): Vector {\n return new Vector(\n Math.max(min.x, Math.min(max.x, this.#x)),\n Math.max(min.y, Math.min(max.y, this.#y)),\n );\n }\n\n /** This function gives a vector with the same value in both components. */\n static of(scalar: number): Vector {\n return new Vector(scalar, scalar);\n }\n\n /** This function gives the smallest value of each component, or a zero vector for an empty list. */\n static min(vectors: Vector[]): Vector {\n if (!vectors.length) return new Vector();\n return new Vector(\n Math.min(...vectors.map((v) => v.x)),\n Math.min(...vectors.map((v) => v.y)),\n );\n }\n\n /** This function gives the largest value of each component, or a zero vector for an empty list. */\n static max(vectors: Vector[]): Vector {\n if (!vectors.length) return new Vector();\n return new Vector(\n Math.max(...vectors.map((v) => v.x)),\n Math.max(...vectors.map((v) => v.y)),\n );\n }\n\n /** This getter gives a new infinite vector each time. A shared value is not safe, because a vector is mutable. */\n static get Infinity(): Vector {\n return new Vector(Infinity, Infinity);\n }\n}\n","import { Vector } from \"./vector.ts\";\n\n/** A rectangle of the packer. `id` identifies the item that it measures. The packer sets `position` and `wasPacked`. */\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- the parameter types the id of the caller\nexport class Rect<T = unknown> {\n readonly #position = new Vector();\n readonly #size = new Vector();\n\n /** The identity of the item that the rectangle measures. */\n id?: T;\n /** The packer sets this field to `true` when it places the rectangle. */\n wasPacked = false;\n /** For an outer rectangle: the packer keeps its width. */\n fixedWidth = false;\n /** For an outer rectangle: the packer keeps its width and its height, and it skips a rectangle that does not fit. */\n fixedSize = false;\n /**\n * The packer keeps the position of a locked rectangle. `packSkyline` packs the other rectangles around it.\n * `packRows` only keeps its position, and other rectangles can overlap it.\n */\n locked = false;\n\n /** The top left corner. */\n get position(): Vector { return this.#position; }\n /** The width and the height. */\n get size(): Vector { return this.#size; }\n\n /** This method gives the right edge. */\n right(): number { return this.#position.x + this.#size.x; }\n /** This method gives the bottom edge. */\n bottom(): number { return this.#position.y + this.#size.y; }\n\n /** This method moves the left edge to the right by an amount, and the right edge stays. */\n shrinkLeft(amount: number): this {\n this.#position.x += amount;\n this.#size.x -= amount;\n return this;\n }\n\n /** This method moves the rectangle by an offset. */\n translateBy(offset: Vector): this {\n this.#position.set(this.#position.x + offset.x, this.#position.y + offset.y);\n return this;\n }\n\n /** This method gives the area of the size. */\n area(): number { return this.#size.area(); }\n\n /** This function gives the rectangles that the packer placed. */\n static packed<U>(rects: Rect<U>[]): Rect<U>[] {\n return rects.filter((r) => r.wasPacked);\n }\n\n /** This function gives the rectangles that the packer did not place. */\n static unpacked<U>(rects: Rect<U>[]): Rect<U>[] {\n return rects.filter((r) => !r.wasPacked);\n }\n\n /** This function marks each rectangle as not placed. A locked rectangle stays placed. */\n static resetPacked<U>(rects: Rect<U>[]): void {\n for (const r of rects) r.wasPacked = r.locked;\n }\n\n /** This function gives the largest right edge, or 0 for an empty list. */\n static maxRight<U>(rects: Rect<U>[]): number {\n return rects.reduce((acc, r) => Math.max(acc, r.right()), 0);\n }\n\n /** This function gives the largest bottom edge, or 0 for an empty list. */\n static maxBottom<U>(rects: Rect<U>[]): number {\n return rects.reduce((acc, r) => Math.max(acc, r.bottom()), 0);\n }\n}\n","import type { Rect } from \"./rect.ts\";\n\n/**\n * An order of the rectangles before the packing. The order changes the layout much. `input` keeps the order of\n * the array of the caller: the row packing then fills no gaps, so its rows read in that order.\n */\nexport type SortOrder = \"height-desc\" | \"height-asc\" | \"width-desc\" | \"area-desc\" | \"input\";\n\n/** This constant lists each sort order. The first one is the default. */\nexport const SORT_ORDERS: readonly SortOrder[] = [\"height-desc\", \"height-asc\", \"width-desc\", \"area-desc\", \"input\"];\n\n/** This function gives the comparator of a sort order. The sort is stable, so `input` keeps the order. */\nexport function compareRects(order: SortOrder): <T>(a: Rect<T>, b: Rect<T>) => number {\n switch (order) {\n case \"height-desc\":\n return (a, b) => b.size.y - a.size.y;\n case \"height-asc\":\n return (a, b) => a.size.y - b.size.y;\n case \"width-desc\":\n return (a, b) => b.size.x - a.size.x;\n case \"area-desc\":\n return (a, b) => b.area() - a.area();\n case \"input\":\n return () => 0;\n }\n}\n","import { compareRects, type SortOrder } from \"./order.ts\";\nimport type { Rect } from \"./rect.ts\";\n\n/** The options of `packShelf` */\nexport interface ShelfOptions {\n /** The order of the rectangles before the packing. The default is `height-desc`. */\n sortOrder?: SortOrder;\n}\n\n/** An obstacle in the coordinates of the outer rectangle */\ninterface Box {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\n/**\n * This function gives the first x from `start` where a rectangle of width `w` crosses no obstacle in the band from\n * `top` to `bottom`.\n */\nfunction firstFreeX(obstacles: readonly Box[], start: number, w: number, top: number, bottom: number): number {\n let x = start;\n let moved = true;\n while (moved) {\n moved = false;\n for (const o of obstacles) {\n if (o.top < bottom && top < o.bottom && o.left < x + w && x < o.right) {\n x = o.right;\n moved = true;\n }\n }\n }\n return x;\n}\n\n/** This function gives the lowest bottom edge of the obstacles in the band from `top` to `bottom`, under `top`. */\nfunction nextTop(obstacles: readonly Box[], top: number, bottom: number): number {\n let next = Infinity;\n for (const o of obstacles) if (o.top < bottom && top < o.bottom && o.bottom > top) next = Math.min(next, o.bottom);\n return next;\n}\n\n/**\n * This function packs rectangles into shelves: rows that it fills left to right in the sort order, and does not fill\n * again. It goes around each `locked` rectangle. It sets the position of each rectangle and gives the outer rectangle.\n *\n * - With the sort order `input`, the rows read in the order of the array: left to right, and top to bottom.\n * - With `height-desc` (the default), it is the classic Next-Fit Decreasing Height packing.\n * - A rectangle goes to the first x in the row where it crosses no locked rectangle. If no such x is in the width,\n * a new row starts under the current row.\n * - A rectangle wider than the outer rectangle goes on its own row, at the left edge, and overflows.\n * - It does not change the width. Without `fixedSize`, the height grows to the content. With `fixedSize`, a\n * rectangle that does not fit is not packed: its `wasPacked` stays `false`.\n */\nexport function packShelf<T>(rects: Rect<T>[], outer: Rect<T>, options: ShelfOptions = {}): Rect<T> {\n const width = outer.size.x;\n const obstacles: Box[] = [];\n let contentBottom = 0;\n for (const rect of rects) {\n if (!rect.locked) continue;\n rect.wasPacked = true;\n const left = rect.position.x - outer.position.x;\n const top = rect.position.y - outer.position.y;\n obstacles.push({ left, top, right: left + rect.size.x, bottom: top + rect.size.y });\n contentBottom = Math.max(contentBottom, top + rect.size.y);\n }\n\n let rowTop = 0;\n let rowBottom = 0;\n let cursor = 0;\n let rowEmpty = true;\n\n for (const rect of rects.toSorted(compareRects(options.sortOrder ?? \"height-desc\"))) {\n if (rect.wasPacked) continue;\n const w = rect.size.x;\n const h = rect.size.y;\n if (!(w > 0 && h > 0)) continue;\n const overflows = w > width;\n if (overflows && outer.fixedSize) continue;\n\n // An overflowing rectangle starts a row of its own\n if (overflows && !rowEmpty) {\n rowTop = rowBottom;\n cursor = 0;\n rowEmpty = true;\n }\n\n let x = firstFreeX(obstacles, cursor, w, rowTop, rowTop + h);\n while (!overflows && x + w > width) {\n // Start a new row under the current one, or under the obstacles that block an empty row\n rowTop = rowEmpty ? nextTop(obstacles, rowTop, rowTop + h) : rowBottom;\n rowBottom = rowTop;\n cursor = 0;\n rowEmpty = true;\n x = firstFreeX(obstacles, cursor, w, rowTop, rowTop + h);\n }\n while (overflows && x !== 0) {\n rowTop = nextTop(obstacles, rowTop, rowTop + h);\n rowBottom = rowTop;\n x = firstFreeX(obstacles, 0, w, rowTop, rowTop + h);\n }\n\n if (outer.fixedSize && rowTop + h > outer.size.y) continue;\n\n rect.position.set(x + outer.position.x, rowTop + outer.position.y);\n rect.wasPacked = true;\n rowEmpty = false;\n cursor = x + w;\n rowBottom = Math.max(rowBottom, rowTop + h);\n contentBottom = Math.max(contentBottom, rowTop + h);\n if (overflows) {\n rowTop = rowBottom;\n cursor = 0;\n rowEmpty = true;\n }\n }\n\n if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, contentBottom);\n return outer;\n}\n","import { compareRects, type SortOrder } from \"./order.ts\";\nimport type { Rect } from \"./rect.ts\";\n\n/**\n * This function finds the lowest window of width `rw` on a skyline. For each `x` in `[0, width - rw]`, the height\n * of the window is the largest value of `skyline[x, x + rw)`. The first `x` with the smallest height wins. A\n * monotonic deque makes the search O(width) for one rectangle.\n */\nexport function lowestWindow(skyline: ArrayLike<number>, width: number, rw: number): { x: number; y: number } {\n const deque = new Int32Array(width);\n let head = 0;\n let tail = 0;\n let bestX = 0;\n let bestY = Infinity;\n for (let i = 0; i < width; i++) {\n while (tail > head && skyline[deque[tail - 1]!]! <= skyline[i]!) tail--;\n deque[tail++] = i;\n if (deque[head]! <= i - rw) head++;\n if (i >= rw - 1) {\n const max = skyline[deque[head]!]!;\n if (max < bestY) {\n bestY = max;\n bestX = i - rw + 1;\n }\n }\n }\n return { x: bestX, y: bestY };\n}\n\n/**\n * This function packs rectangles into an outer rectangle with a skyline. It puts each rectangle in the lowest place\n * where it fits, and it does not change the width of the outer rectangle.\n *\n * - A `locked` rectangle keeps its position. The skyline starts at its bottom edge across its width, thus the other\n * rectangles go under it. The space above it stays empty, because a skyline has no holes.\n * - The skyline has a cell for each whole unit of the width. A rectangle that needs more cells than the outer\n * rectangle has goes under all other rectangles, at the left edge.\n * - Without `fixedSize`, the height of the outer rectangle grows to the content. With `fixedSize`, a rectangle that\n * does not fit is not packed: its `wasPacked` stays `false`.\n * - The default sort order is `height-desc`. The order of the array of the caller stays.\n */\nexport function packSkyline<T>(rects: Rect<T>[], outer: Rect<T>, options: { sortOrder?: SortOrder } = {}): Rect<T> {\n // a cell for each whole unit of the width: a rectangle never goes past the right edge\n const width = Math.floor(outer.size.x);\n if (width <= 0) return outer;\n const skyline = new Float64Array(width);\n\n for (const rect of rects) {\n if (!rect.locked) continue;\n rect.wasPacked = true;\n const x0 = Math.max(0, Math.floor(rect.position.x - outer.position.x));\n const x1 = Math.min(width, Math.ceil(rect.position.x - outer.position.x + rect.size.x));\n const bottom = rect.position.y - outer.position.y + rect.size.y;\n for (let i = x0; i < x1; i++) skyline[i] = Math.max(skyline[i]!, bottom);\n if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, bottom);\n }\n\n for (const rect of rects.toSorted(compareRects(options.sortOrder ?? \"height-desc\"))) {\n if (rect.wasPacked) continue;\n const rw = Math.ceil(rect.size.x);\n const rh = rect.size.y;\n if (rw <= 0 || rh <= 0) continue;\n\n let x: number;\n let y: number;\n if (rw > width) {\n if (outer.fixedSize) continue;\n x = 0;\n y = 0;\n for (let i = 0; i < width; i++) if (skyline[i]! > y) y = skyline[i]!;\n } else {\n ({ x, y } = lowestWindow(skyline, width, rw));\n if (outer.fixedSize && y + rh > outer.size.y) continue;\n }\n\n rect.position.set(x + outer.position.x, y + outer.position.y);\n rect.wasPacked = true;\n const top = y + rh;\n for (let i = x; i < Math.min(width, x + rw); i++) skyline[i] = top;\n if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, top);\n }\n return outer;\n}\n","import { compareRects, type SortOrder } from \"./order.ts\";\nimport { Rect } from \"./rect.ts\";\nimport { packShelf } from \"./shelf.ts\";\nimport { packSkyline } from \"./skyline.ts\";\nimport { Vector } from \"./vector.ts\";\n\n/** The options of `pack` and `packRows` */\nexport interface PackOptions {\n /** The order of the rectangles before the packing. The default is `height-desc`. */\n sortOrder?: SortOrder;\n}\n\n/** The options of `pack` */\nexport interface PackLayoutOptions extends PackOptions {\n /**\n * The packing: `rows` (the default), `skyline` or `shelf`. `skyline` and `shelf` pack around locked rectangles.\n * Use `shelf` with the sort order `input` to keep the order of the array around locked rectangles.\n */\n strategy?: \"rows\" | \"skyline\" | \"shelf\";\n}\n\nfunction packInto<T>(rects: Rect<T>[], region: Rect<T>, offset: Vector, order: SortOrder): void {\n const unpacked = Rect.unpacked(rects);\n pack(unpacked, region, { sortOrder: order });\n for (const r of Rect.packed(unpacked)) r.translateBy(offset);\n}\n\nfunction packRow<T>(rects: Rect<T>[], outer: Rect<T>, order: SortOrder): boolean {\n const row: Rect<T>[] = [];\n // toSorted gives a sorted copy, thus the order of the array of the caller stays\n const byHeight = rects.toSorted(compareRects(order));\n // A gap fill puts a later rect before an earlier one, thus the order `input` fills no gaps\n const fillGaps = order !== \"input\";\n\n const rowRect = new Rect<T>();\n rowRect.size.set(outer.size.x, 0);\n\n for (const rect of byHeight) {\n if (rect.wasPacked) continue;\n\n if (rect.size.x > rowRect.size.x) {\n if (outer.fixedSize && rect.size.x > outer.size.x) continue;\n\n const outerLeft = new Rect<T>();\n outerLeft.position.setTo(rowRect.position);\n outerLeft.size.set(outer.size.x - rowRect.position.x, rowRect.size.y);\n outerLeft.fixedSize = true;\n if (fillGaps) packInto(rects, outerLeft, outer.position, order);\n\n rowRect.position.set(0, rowRect.bottom());\n rowRect.size.set(outer.size.x, 0);\n row.length = 0;\n }\n\n if (rowRect.position.y + rect.size.y > outer.size.y) {\n if (outer.fixedSize) continue;\n outer.size.y += rect.size.y;\n if (!outer.fixedWidth && !outer.size.x) outer.size.x = rect.size.x;\n Rect.resetPacked(rects);\n return false;\n }\n\n row.push(rect);\n rect.position.setTo(rowRect.position.add(outer.position));\n rowRect.shrinkLeft(rect.size.x);\n rect.wasPacked = true;\n\n if (rect.size.y > rowRect.size.y) rowRect.size.y = rect.size.y;\n\n if (fillGaps && row.length > 1) {\n const rectInRow = row[row.length - 1]!;\n const outerLeft = new Rect<T>();\n outerLeft.position.set(rectInRow.position.x, rectInRow.bottom());\n outerLeft.size.set(rectInRow.size.x, rowRect.size.y - rectInRow.size.y);\n outerLeft.fixedWidth = true;\n outerLeft.fixedSize = true;\n packInto(rects, outerLeft, outer.position, order);\n }\n }\n return true;\n}\n\n/**\n * This function packs rectangles into an outer rectangle of a given width with a guillotine row packing. It does\n * not change the width. Without `fixedSize`, the height grows until all rectangles fit. A `locked` rectangle keeps\n * its position, but the rows do not go around it. `pack` sets the width first and then uses this function.\n */\nexport function packRows<T>(rects: Rect<T>[], outer: Rect<T>, options: PackOptions = {}): Rect<T> {\n const order = options.sortOrder ?? \"height-desc\";\n for (const r of rects) if (r.locked) r.wasPacked = true;\n while (!packRow(rects, outer, order));\n return outer;\n}\n\n/**\n * This function packs rectangles into an outer rectangle with a guillotine row packing. It sets the position\n * of each rectangle and the size of the outer rectangle, and it gives the outer rectangle.\n *\n * - Without fixed sizes, the outer rectangle starts with a width of 1.5 times the square root of the total area.\n * This width is not less than the width of the widest rectangle. The height grows until all rectangles fit.\n * - With `fixedWidth`, the width stays. A rectangle wider than that width goes on its own row and overflows.\n * - With `fixedSize`, a rectangle that does not fit is not packed: its `wasPacked` stays `false`.\n * - `options.strategy` selects `packRows` (the default), `packSkyline` or `packShelf` after the width is set.\n */\nexport function pack<T>(rects: Rect<T>[], outer: Rect<T> = new Rect(), options: PackLayoutOptions = {}): Rect<T> {\n let totalArea = 0;\n let widest = 0;\n let minX = rects.length > 0 ? Infinity : 0;\n let minY = rects.length > 0 ? Infinity : 0;\n for (const r of rects) {\n totalArea += r.area();\n widest = Math.max(widest, r.size.x);\n minX = Math.min(minX, r.size.x);\n minY = Math.min(minY, r.size.y);\n }\n\n const idealWidth = outer.fixedWidth\n ? outer.size.x\n : Math.max(widest, Math.min(1.5 * Math.sqrt(totalArea), outer.size.x || Infinity));\n\n const ideal = Vector.of(idealWidth).clamp(new Vector(minX, minY), Vector.Infinity);\n\n // A fixed width stays, also when a rectangle is wider: that rectangle overflows\n if (!outer.fixedSize) outer.size.setTo(outer.fixedWidth ? new Vector(outer.size.x, ideal.y) : ideal);\n if (options.strategy === \"skyline\") packSkyline(rects, outer, options);\n else if (options.strategy === \"shelf\") packShelf(rects, outer, options);\n else packRows(rects, outer, options);\n\n if (!outer.fixedSize) {\n const contentRight = Rect.maxRight(rects);\n if (!outer.size.x) outer.size.x = contentRight;\n else if (!outer.fixedWidth) outer.size.x = Math.min(contentRight, outer.size.x);\n outer.size.y = Rect.maxBottom(rects);\n }\n\n return outer;\n}\n","import { pack, type PackLayoutOptions } from \"./pack.ts\";\nimport { Rect } from \"./rect.ts\";\n\n/** The options of `packFree` */\nexport interface FreeWidthOptions extends PackLayoutOptions {\n /** The target aspect: the width divided by the height. The default is 1, a square. */\n aspect?: number;\n /**\n * The weight of the aspect error in the objective. The objective is the area times `1 + aspectWeight * aspectError`.\n * The default is 1. With 0, only the area counts.\n */\n aspectWeight?: number;\n /** The number of widths that the first pass tries. The default is 48. */\n samples?: number;\n}\n\n/** One width that `packFree` tried, and its result */\nexport interface FreeWidthTry {\n /** The width of the container for this try */\n width: number;\n /** The width of the content: the right edge of the rectangles */\n contentWidth: number;\n /** The height of the content */\n contentHeight: number;\n /** The objective of this try. Lower is better. */\n objective: number;\n}\n\n/** The result of `packFree` */\nexport interface FreeWidthResult<T> {\n /** The outer rectangle, with the size of the content */\n outer: Rect<T>;\n /** The area of the bounding box of the content */\n area: number;\n /** The area of the rectangles divided by the area of the bounding box */\n fill: number;\n /** The aspect of the bounding box: the width divided by the height */\n aspect: number;\n /** The distance from the target aspect: the absolute value of `log(aspect) - log(target)` */\n aspectError: number;\n /** The objective of the chosen width. Lower is better. */\n objective: number;\n /** Each width that the search tried, in the order of the search */\n tried: FreeWidthTry[];\n}\n\n/** This function gives the distance between an aspect and a target aspect on a log scale. */\nexport function aspectError(width: number, height: number, target = 1): number {\n if (!(width > 0 && height > 0 && target > 0)) return Infinity;\n return Math.abs(Math.log(width / height) - Math.log(target));\n}\n\n/**\n * This function packs rectangles with a free width. It tries many widths with the strategy of `options`, and keeps\n * the width with the lowest objective: the area of the bounding box times `1 + aspectWeight * aspectError`.\n *\n * - The first pass tries `samples` widths on a log scale, from the widest rectangle to the width of one row.\n * - The second pass tries the widths between the two neighbours of the best width of the first pass.\n * - The search is deterministic: the same input gives the same widths and the same result.\n * - A `locked` rectangle keeps its position. The width is not less than its right edge.\n * - Each strategy of `pack` works with this function, because it only changes the width.\n */\nexport function packFree<T>(rects: Rect<T>[], outer: Rect<T> = new Rect(), options: FreeWidthOptions = {}): FreeWidthResult<T> {\n const target = options.aspect ?? 1;\n const weight = options.aspectWeight ?? 1;\n const samples = Math.max(2, Math.floor(options.samples ?? 48));\n const ox = outer.position.x;\n const oy = outer.position.y;\n\n let itemArea = 0;\n let minWidth = 0;\n let rowWidth = 0;\n for (const r of rects) {\n if (r.locked) {\n minWidth = Math.max(minWidth, r.right() - ox);\n continue;\n }\n if (!(r.size.x > 0 && r.size.y > 0)) continue;\n itemArea += r.area();\n minWidth = Math.max(minWidth, r.size.x);\n rowWidth += r.size.x;\n }\n for (const r of rects) if (r.locked) itemArea += r.area();\n const maxWidth = Math.max(minWidth, rowWidth);\n\n const tried: FreeWidthTry[] = [];\n const run = (width: number): FreeWidthTry => {\n Rect.resetPacked(rects);\n const box = new Rect<T>();\n box.position.setTo(outer.position);\n box.size.set(width, 0);\n box.fixedWidth = true;\n pack(rects, box, options);\n const placed = Rect.packed(rects);\n const contentWidth = Math.max(0, Rect.maxRight(placed) - ox);\n const contentHeight = Math.max(0, Rect.maxBottom(placed) - oy);\n const area = contentWidth * contentHeight;\n const objective = area > 0 ? area * (1 + weight * aspectError(contentWidth, contentHeight, target)) : 0;\n const t = { width, contentWidth, contentHeight, objective };\n tried.push(t);\n return t;\n };\n\n // The first pass: widths on a log scale. Each width is a whole number when the sizes are whole numbers.\n const widths: number[] = [];\n if (maxWidth > 0) {\n const ratio = maxWidth / minWidth;\n for (let i = 0; i < samples; i++) {\n const w = minWidth * ratio ** (i / (samples - 1));\n const rounded = i === samples - 1 ? maxWidth : Math.max(minWidth, Math.round(w));\n if (widths.at(-1) !== rounded) widths.push(rounded);\n }\n }\n let best: FreeWidthTry | undefined = undefined;\n let bestIndex = 0;\n for (const [i, w] of widths.entries()) {\n const t = run(w);\n if (best === undefined || t.objective < best.objective) {\n best = t;\n bestIndex = i;\n }\n }\n\n // The second pass: the widths between the two neighbours of the best width\n if (widths.length > 2) {\n const low = widths[Math.max(0, bestIndex - 1)]!;\n const high = widths[Math.min(widths.length - 1, bestIndex + 1)]!;\n const steps = Math.min(Math.floor(samples / 2), Math.max(0, Math.round(high - low) - 1));\n for (let i = 1; i <= steps; i++) {\n const w = Math.round(low + ((high - low) * i) / (steps + 1));\n if (widths.includes(w)) continue;\n const t = run(w);\n if (best === undefined || t.objective < best.objective) best = t;\n }\n }\n\n // The last run packs with the best width, so the positions of the rectangles are those of the best width\n const chosen = best === undefined ? undefined : run(best.width);\n tried.pop();\n const w = chosen?.contentWidth ?? 0;\n const h = chosen?.contentHeight ?? 0;\n if (!outer.fixedSize) outer.size.set(w, h);\n const area = w * h;\n return {\n outer,\n area,\n fill: area > 0 ? itemArea / area : 0,\n aspect: h > 0 ? w / h : 0,\n aspectError: aspectError(w, h, target),\n objective: chosen?.objective ?? 0,\n tried,\n };\n}\n","import type { Rect } from \"./rect.ts\";\n\n/** The options of `pushCascade`. */\nexport interface PushOptions {\n /** The space between a shoved rectangle and the rectangle that shoves it. The default is 8. */\n gap?: number;\n /** The most shoves. The default is eight for each rectangle. The cascade stops at this limit. */\n maxShoves?: number;\n}\n\n/** A direction of a shove: an axis and a sign. */\ninterface Direction {\n readonly axis: \"x\" | \"y\";\n readonly sign: 1 | -1;\n}\n\n/** This function gives the overlap of two rectangles on each axis. A value of 0 or less is no overlap. */\nfunction overlapOf(a: Rect, b: Rect): { x: number; y: number } {\n return {\n x: Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x),\n y: Math.min(a.bottom(), b.bottom()) - Math.max(a.position.y, b.position.y),\n };\n}\n\n/**\n * This function gives the direction of a first shove. The axis has the smaller overlap. The sign points away from the\n * center of the pusher. When that shove crosses 0, the sign is positive.\n */\nfunction firstDirection(pusher: Rect, r: Rect, gap: number): Direction {\n const overlap = overlapOf(pusher, r);\n if (overlap.x < overlap.y) {\n const back = r.position.x + r.size.x / 2 < pusher.position.x + pusher.size.x / 2 && pusher.position.x - r.size.x - gap >= 0;\n return { axis: \"x\", sign: back ? -1 : 1 };\n }\n const back = r.position.y + r.size.y / 2 < pusher.position.y + pusher.size.y / 2 && pusher.position.y - r.size.y - gap >= 0;\n return { axis: \"y\", sign: back ? -1 : 1 };\n}\n\n/** This function puts a rectangle past the pusher, along a direction. The rectangle does not go past 0. */\nfunction shove(pusher: Rect, r: Rect, direction: Direction, gap: number): void {\n if (direction.axis === \"x\") {\n const x = direction.sign > 0 ? pusher.right() + gap : pusher.position.x - r.size.x - gap;\n r.position.set(Math.max(0, x), r.position.y);\n return;\n }\n const y = direction.sign > 0 ? pusher.bottom() + gap : pusher.position.y - r.size.y - gap;\n r.position.set(r.position.x, Math.max(0, y));\n}\n\n/**\n * This function moves each rectangle that a placed rectangle overlaps, and the rectangles that those overlap in turn.\n * A shove takes the axis of the smaller overlap, away from the pusher. A shoved rectangle keeps the direction of its first\n * shove when it shoves the next one, so the cascade goes outward. The placed rectangle and each locked rectangle stay.\n * The function moves the rectangles in place and gives the rectangles that moved.\n *\n * @example\n * ```ts\n * const moved = pushCascade(dropped, cards, { gap: 8 });\n * ```\n */\nexport function pushCascade<T>(placed: Rect<T>, rects: readonly Rect<T>[], options: PushOptions = {}): Rect<T>[] {\n const gap = options.gap ?? 8;\n const maxShoves = options.maxShoves ?? rects.length * 8;\n const moved = new Set<Rect<T>>();\n const queue: { pusher: Rect<T>; direction?: Direction }[] = [{ pusher: placed }];\n let shoves = 0;\n while (shoves < maxShoves) {\n const step = queue.shift();\n if (!step) break;\n const { pusher, direction } = step;\n for (const r of rects) {\n if (r === pusher || r === placed || r.locked) continue;\n const overlap = overlapOf(pusher, r);\n if (overlap.x <= 0 || overlap.y <= 0) continue;\n const next = direction ?? firstDirection(pusher, r, gap);\n shove(pusher, r, next, gap);\n moved.add(r);\n queue.push({ pusher: r, direction: next });\n if (++shoves >= maxShoves) break;\n }\n }\n return [...moved];\n}\n","import type { Rect } from \"./rect.ts\";\n\n/** The measurements of a packing. */\nexport interface PackingMetrics {\n /** The number of rectangles. */\n count: number;\n /** The size of the hull: the smallest box around the rectangles. */\n hull: { width: number; height: number };\n /** The width of the hull divided by its height. A hull without height gives 0. */\n hullAspect: number;\n /** The number of rectangles in each row, from the top. */\n rows: number[];\n /** The largest right edge of a row less the smallest right edge of a row. A straight right side gives 0. */\n raggedness: number;\n /** The part of the hull that the rectangles cover: their area divided by the area of the hull. */\n utilization: number;\n /** The width of the widest rectangle divided by the median width. */\n widestOverMedian: number;\n}\n\n/** The options of `packingMetrics`. */\nexport interface MetricsOptions {\n /** A rectangle joins a row when its top is this close to the top of the first rectangle of the row. The default is 40. */\n rowTolerance?: number;\n}\n\n/**\n * This function measures a packing. It gives the hull and its aspect, the rows and the raggedness of the right side.\n * It also gives the part of the hull that the rectangles cover, and the widest rectangle against the median width.\n *\n * @example\n * ```ts\n * const { utilization, raggedness } = packingMetrics(Rect.packed(cards));\n * ```\n */\nexport function packingMetrics(rects: readonly Rect[], options: MetricsOptions = {}): PackingMetrics {\n const tolerance = options.rowTolerance ?? 40;\n const boxes = rects.filter((r) => r.size.x > 0 && r.size.y > 0);\n if (boxes.length === 0) return { count: 0, hull: { width: 0, height: 0 }, hullAspect: 0, rows: [], raggedness: 0, utilization: 0, widestOverMedian: 0 };\n\n const left = Math.min(...boxes.map((r) => r.position.x));\n const top = Math.min(...boxes.map((r) => r.position.y));\n const right = Math.max(...boxes.map((r) => r.right()));\n const bottom = Math.max(...boxes.map((r) => r.bottom()));\n const hull = { width: right - left, height: bottom - top };\n\n // The rows: the rectangles by their top, and a row takes each rectangle near the top of its first rectangle\n const rows: { top: number; right: number; count: number }[] = [];\n for (const r of boxes.toSorted((a, b) => a.position.y - b.position.y)) {\n const row = rows.at(-1);\n if (row && Math.abs(r.position.y - row.top) <= tolerance) {\n row.right = Math.max(row.right, r.right());\n row.count += 1;\n } else {\n rows.push({ top: r.position.y, right: r.right(), count: 1 });\n }\n }\n const rights = rows.map((row) => row.right);\n const widths = boxes.map((r) => r.size.x).toSorted((a, b) => a - b);\n const median = widths[Math.floor(widths.length / 2)] ?? 0;\n const widest = widths.at(-1) ?? 0;\n const area = boxes.reduce((sum, r) => sum + r.area(), 0);\n return {\n count: boxes.length,\n hull,\n hullAspect: hull.height > 0 ? hull.width / hull.height : 0,\n rows: rows.map((row) => row.count),\n raggedness: Math.max(...rights) - Math.min(...rights),\n utilization: hull.width * hull.height > 0 ? area / (hull.width * hull.height) : 0,\n widestOverMedian: median > 0 ? widest / median : 0,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/packing",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "Rectangle packing: shelf, guillotine and skyline strategies, push cascades and measures of a layout.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rectangle-packing",
|
package/src/free.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { pack, type PackLayoutOptions } from "./pack.ts";
|
|
2
|
+
import { Rect } from "./rect.ts";
|
|
3
|
+
|
|
4
|
+
/** The options of `packFree` */
|
|
5
|
+
export interface FreeWidthOptions extends PackLayoutOptions {
|
|
6
|
+
/** The target aspect: the width divided by the height. The default is 1, a square. */
|
|
7
|
+
aspect?: number;
|
|
8
|
+
/**
|
|
9
|
+
* The weight of the aspect error in the objective. The objective is the area times `1 + aspectWeight * aspectError`.
|
|
10
|
+
* The default is 1. With 0, only the area counts.
|
|
11
|
+
*/
|
|
12
|
+
aspectWeight?: number;
|
|
13
|
+
/** The number of widths that the first pass tries. The default is 48. */
|
|
14
|
+
samples?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** One width that `packFree` tried, and its result */
|
|
18
|
+
export interface FreeWidthTry {
|
|
19
|
+
/** The width of the container for this try */
|
|
20
|
+
width: number;
|
|
21
|
+
/** The width of the content: the right edge of the rectangles */
|
|
22
|
+
contentWidth: number;
|
|
23
|
+
/** The height of the content */
|
|
24
|
+
contentHeight: number;
|
|
25
|
+
/** The objective of this try. Lower is better. */
|
|
26
|
+
objective: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The result of `packFree` */
|
|
30
|
+
export interface FreeWidthResult<T> {
|
|
31
|
+
/** The outer rectangle, with the size of the content */
|
|
32
|
+
outer: Rect<T>;
|
|
33
|
+
/** The area of the bounding box of the content */
|
|
34
|
+
area: number;
|
|
35
|
+
/** The area of the rectangles divided by the area of the bounding box */
|
|
36
|
+
fill: number;
|
|
37
|
+
/** The aspect of the bounding box: the width divided by the height */
|
|
38
|
+
aspect: number;
|
|
39
|
+
/** The distance from the target aspect: the absolute value of `log(aspect) - log(target)` */
|
|
40
|
+
aspectError: number;
|
|
41
|
+
/** The objective of the chosen width. Lower is better. */
|
|
42
|
+
objective: number;
|
|
43
|
+
/** Each width that the search tried, in the order of the search */
|
|
44
|
+
tried: FreeWidthTry[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** This function gives the distance between an aspect and a target aspect on a log scale. */
|
|
48
|
+
export function aspectError(width: number, height: number, target = 1): number {
|
|
49
|
+
if (!(width > 0 && height > 0 && target > 0)) return Infinity;
|
|
50
|
+
return Math.abs(Math.log(width / height) - Math.log(target));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* This function packs rectangles with a free width. It tries many widths with the strategy of `options`, and keeps
|
|
55
|
+
* the width with the lowest objective: the area of the bounding box times `1 + aspectWeight * aspectError`.
|
|
56
|
+
*
|
|
57
|
+
* - The first pass tries `samples` widths on a log scale, from the widest rectangle to the width of one row.
|
|
58
|
+
* - The second pass tries the widths between the two neighbours of the best width of the first pass.
|
|
59
|
+
* - The search is deterministic: the same input gives the same widths and the same result.
|
|
60
|
+
* - A `locked` rectangle keeps its position. The width is not less than its right edge.
|
|
61
|
+
* - Each strategy of `pack` works with this function, because it only changes the width.
|
|
62
|
+
*/
|
|
63
|
+
export function packFree<T>(rects: Rect<T>[], outer: Rect<T> = new Rect(), options: FreeWidthOptions = {}): FreeWidthResult<T> {
|
|
64
|
+
const target = options.aspect ?? 1;
|
|
65
|
+
const weight = options.aspectWeight ?? 1;
|
|
66
|
+
const samples = Math.max(2, Math.floor(options.samples ?? 48));
|
|
67
|
+
const ox = outer.position.x;
|
|
68
|
+
const oy = outer.position.y;
|
|
69
|
+
|
|
70
|
+
let itemArea = 0;
|
|
71
|
+
let minWidth = 0;
|
|
72
|
+
let rowWidth = 0;
|
|
73
|
+
for (const r of rects) {
|
|
74
|
+
if (r.locked) {
|
|
75
|
+
minWidth = Math.max(minWidth, r.right() - ox);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!(r.size.x > 0 && r.size.y > 0)) continue;
|
|
79
|
+
itemArea += r.area();
|
|
80
|
+
minWidth = Math.max(minWidth, r.size.x);
|
|
81
|
+
rowWidth += r.size.x;
|
|
82
|
+
}
|
|
83
|
+
for (const r of rects) if (r.locked) itemArea += r.area();
|
|
84
|
+
const maxWidth = Math.max(minWidth, rowWidth);
|
|
85
|
+
|
|
86
|
+
const tried: FreeWidthTry[] = [];
|
|
87
|
+
const run = (width: number): FreeWidthTry => {
|
|
88
|
+
Rect.resetPacked(rects);
|
|
89
|
+
const box = new Rect<T>();
|
|
90
|
+
box.position.setTo(outer.position);
|
|
91
|
+
box.size.set(width, 0);
|
|
92
|
+
box.fixedWidth = true;
|
|
93
|
+
pack(rects, box, options);
|
|
94
|
+
const placed = Rect.packed(rects);
|
|
95
|
+
const contentWidth = Math.max(0, Rect.maxRight(placed) - ox);
|
|
96
|
+
const contentHeight = Math.max(0, Rect.maxBottom(placed) - oy);
|
|
97
|
+
const area = contentWidth * contentHeight;
|
|
98
|
+
const objective = area > 0 ? area * (1 + weight * aspectError(contentWidth, contentHeight, target)) : 0;
|
|
99
|
+
const t = { width, contentWidth, contentHeight, objective };
|
|
100
|
+
tried.push(t);
|
|
101
|
+
return t;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// The first pass: widths on a log scale. Each width is a whole number when the sizes are whole numbers.
|
|
105
|
+
const widths: number[] = [];
|
|
106
|
+
if (maxWidth > 0) {
|
|
107
|
+
const ratio = maxWidth / minWidth;
|
|
108
|
+
for (let i = 0; i < samples; i++) {
|
|
109
|
+
const w = minWidth * ratio ** (i / (samples - 1));
|
|
110
|
+
const rounded = i === samples - 1 ? maxWidth : Math.max(minWidth, Math.round(w));
|
|
111
|
+
if (widths.at(-1) !== rounded) widths.push(rounded);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let best: FreeWidthTry | undefined = undefined;
|
|
115
|
+
let bestIndex = 0;
|
|
116
|
+
for (const [i, w] of widths.entries()) {
|
|
117
|
+
const t = run(w);
|
|
118
|
+
if (best === undefined || t.objective < best.objective) {
|
|
119
|
+
best = t;
|
|
120
|
+
bestIndex = i;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// The second pass: the widths between the two neighbours of the best width
|
|
125
|
+
if (widths.length > 2) {
|
|
126
|
+
const low = widths[Math.max(0, bestIndex - 1)]!;
|
|
127
|
+
const high = widths[Math.min(widths.length - 1, bestIndex + 1)]!;
|
|
128
|
+
const steps = Math.min(Math.floor(samples / 2), Math.max(0, Math.round(high - low) - 1));
|
|
129
|
+
for (let i = 1; i <= steps; i++) {
|
|
130
|
+
const w = Math.round(low + ((high - low) * i) / (steps + 1));
|
|
131
|
+
if (widths.includes(w)) continue;
|
|
132
|
+
const t = run(w);
|
|
133
|
+
if (best === undefined || t.objective < best.objective) best = t;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The last run packs with the best width, so the positions of the rectangles are those of the best width
|
|
138
|
+
const chosen = best === undefined ? undefined : run(best.width);
|
|
139
|
+
tried.pop();
|
|
140
|
+
const w = chosen?.contentWidth ?? 0;
|
|
141
|
+
const h = chosen?.contentHeight ?? 0;
|
|
142
|
+
if (!outer.fixedSize) outer.size.set(w, h);
|
|
143
|
+
const area = w * h;
|
|
144
|
+
return {
|
|
145
|
+
outer,
|
|
146
|
+
area,
|
|
147
|
+
fill: area > 0 ? itemArea / area : 0,
|
|
148
|
+
aspect: h > 0 ? w / h : 0,
|
|
149
|
+
aspectError: aspectError(w, h, target),
|
|
150
|
+
objective: chosen?.objective ?? 0,
|
|
151
|
+
tried,
|
|
152
|
+
};
|
|
153
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { Rect } from "./rect.ts";
|
|
|
3
3
|
export { pack, packRows, type PackOptions, type PackLayoutOptions } from "./pack.ts";
|
|
4
4
|
export { packSkyline, lowestWindow } from "./skyline.ts";
|
|
5
5
|
export { packShelf, type ShelfOptions } from "./shelf.ts";
|
|
6
|
+
export { packFree, aspectError, type FreeWidthOptions, type FreeWidthResult, type FreeWidthTry } from "./free.ts";
|
|
6
7
|
export { compareRects, SORT_ORDERS, type SortOrder } from "./order.ts";
|
|
7
8
|
export { pushCascade, type PushOptions } from "./push.ts";
|
|
8
9
|
export { packingMetrics, type MetricsOptions, type PackingMetrics } from "./metrics.ts";
|