@mark1russell7/packing 0.0.0-bootstrap.0 → 0.1.0
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/LICENSE +21 -0
- package/README.md +65 -1
- package/build/index.d.ts +188 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +462 -0
- package/build/index.js.map +1 -0
- package/package.json +55 -4
- package/src/index.ts +7 -0
- package/src/metrics.ts +72 -0
- package/src/order.ts +26 -0
- package/src/pack.ts +132 -0
- package/src/push.ts +83 -0
- package/src/rect.ts +73 -0
- package/src/skyline.ts +83 -0
- package/src/vector.ts +80 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// src/vector.ts
|
|
2
|
+
/** A mutable two-dimensional vector. */
|
|
3
|
+
var Vector = class Vector {
|
|
4
|
+
#x;
|
|
5
|
+
#y;
|
|
6
|
+
constructor(x = 0, y = 0) {
|
|
7
|
+
this.#x = x;
|
|
8
|
+
this.#y = y;
|
|
9
|
+
}
|
|
10
|
+
/** The first component: a width or a left position. */
|
|
11
|
+
get x() {
|
|
12
|
+
return this.#x;
|
|
13
|
+
}
|
|
14
|
+
set x(v) {
|
|
15
|
+
this.#x = v;
|
|
16
|
+
}
|
|
17
|
+
/** The second component: a height or a top position. */
|
|
18
|
+
get y() {
|
|
19
|
+
return this.#y;
|
|
20
|
+
}
|
|
21
|
+
set y(v) {
|
|
22
|
+
this.#y = v;
|
|
23
|
+
}
|
|
24
|
+
/** This method sets both components. */
|
|
25
|
+
set(x, y) {
|
|
26
|
+
this.#x = x;
|
|
27
|
+
this.#y = y;
|
|
28
|
+
}
|
|
29
|
+
/** This method copies the components of another vector. */
|
|
30
|
+
setTo(v) {
|
|
31
|
+
this.#x = v.x;
|
|
32
|
+
this.#y = v.y;
|
|
33
|
+
}
|
|
34
|
+
/** This method gives a new vector: the sum of this vector and another. */
|
|
35
|
+
add(other) {
|
|
36
|
+
return new Vector(this.#x + other.x, this.#y + other.y);
|
|
37
|
+
}
|
|
38
|
+
/** This method gives the product of the components: the area of a size. */
|
|
39
|
+
area() {
|
|
40
|
+
return this.#x * this.#y;
|
|
41
|
+
}
|
|
42
|
+
/** This method gives a new vector with the same components. */
|
|
43
|
+
clone() {
|
|
44
|
+
return new Vector(this.#x, this.#y);
|
|
45
|
+
}
|
|
46
|
+
/** This method gives a new vector with each component between the components of `min` and `max`. */
|
|
47
|
+
clamp(min, max) {
|
|
48
|
+
return new Vector(Math.max(min.x, Math.min(max.x, this.#x)), Math.max(min.y, Math.min(max.y, this.#y)));
|
|
49
|
+
}
|
|
50
|
+
/** This function gives a vector with the same value in both components. */
|
|
51
|
+
static of(scalar) {
|
|
52
|
+
return new Vector(scalar, scalar);
|
|
53
|
+
}
|
|
54
|
+
/** This function gives the smallest value of each component, or a zero vector for an empty list. */
|
|
55
|
+
static min(vectors) {
|
|
56
|
+
if (!vectors.length) return new Vector();
|
|
57
|
+
return new Vector(Math.min(...vectors.map((v) => v.x)), Math.min(...vectors.map((v) => v.y)));
|
|
58
|
+
}
|
|
59
|
+
/** This function gives the largest value of each component, or a zero vector for an empty list. */
|
|
60
|
+
static max(vectors) {
|
|
61
|
+
if (!vectors.length) return new Vector();
|
|
62
|
+
return new Vector(Math.max(...vectors.map((v) => v.x)), Math.max(...vectors.map((v) => v.y)));
|
|
63
|
+
}
|
|
64
|
+
/** This getter gives a new infinite vector each time. A shared value is not safe, because a vector is mutable. */
|
|
65
|
+
static get Infinity() {
|
|
66
|
+
return new Vector(Infinity, Infinity);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
// src/rect.ts
|
|
70
|
+
/** A rectangle of the packer. `id` identifies the item that it measures. The packer sets `position` and `wasPacked`. */
|
|
71
|
+
var Rect = class {
|
|
72
|
+
#position = new Vector();
|
|
73
|
+
#size = new Vector();
|
|
74
|
+
/** The identity of the item that the rectangle measures. */
|
|
75
|
+
id;
|
|
76
|
+
/** The packer sets this field to `true` when it places the rectangle. */
|
|
77
|
+
wasPacked = false;
|
|
78
|
+
/** For an outer rectangle: the packer keeps its width. */
|
|
79
|
+
fixedWidth = false;
|
|
80
|
+
/** For an outer rectangle: the packer keeps its width and its height, and it skips a rectangle that does not fit. */
|
|
81
|
+
fixedSize = false;
|
|
82
|
+
/**
|
|
83
|
+
* The packer keeps the position of a locked rectangle. `packSkyline` packs the other rectangles around it.
|
|
84
|
+
* `packRows` only keeps its position, and other rectangles can overlap it.
|
|
85
|
+
*/
|
|
86
|
+
locked = false;
|
|
87
|
+
/** The top left corner. */
|
|
88
|
+
get position() {
|
|
89
|
+
return this.#position;
|
|
90
|
+
}
|
|
91
|
+
/** The width and the height. */
|
|
92
|
+
get size() {
|
|
93
|
+
return this.#size;
|
|
94
|
+
}
|
|
95
|
+
/** This method gives the right edge. */
|
|
96
|
+
right() {
|
|
97
|
+
return this.#position.x + this.#size.x;
|
|
98
|
+
}
|
|
99
|
+
/** This method gives the bottom edge. */
|
|
100
|
+
bottom() {
|
|
101
|
+
return this.#position.y + this.#size.y;
|
|
102
|
+
}
|
|
103
|
+
/** This method moves the left edge to the right by an amount, and the right edge stays. */
|
|
104
|
+
shrinkLeft(amount) {
|
|
105
|
+
this.#position.x += amount;
|
|
106
|
+
this.#size.x -= amount;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
/** This method moves the rectangle by an offset. */
|
|
110
|
+
translateBy(offset) {
|
|
111
|
+
this.#position.set(this.#position.x + offset.x, this.#position.y + offset.y);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
/** This method gives the area of the size. */
|
|
115
|
+
area() {
|
|
116
|
+
return this.#size.area();
|
|
117
|
+
}
|
|
118
|
+
/** This function gives the rectangles that the packer placed. */
|
|
119
|
+
static packed(rects) {
|
|
120
|
+
return rects.filter((r) => r.wasPacked);
|
|
121
|
+
}
|
|
122
|
+
/** This function gives the rectangles that the packer did not place. */
|
|
123
|
+
static unpacked(rects) {
|
|
124
|
+
return rects.filter((r) => !r.wasPacked);
|
|
125
|
+
}
|
|
126
|
+
/** This function marks each rectangle as not placed. A locked rectangle stays placed. */
|
|
127
|
+
static resetPacked(rects) {
|
|
128
|
+
for (const r of rects) r.wasPacked = r.locked;
|
|
129
|
+
}
|
|
130
|
+
/** This function gives the largest right edge, or 0 for an empty list. */
|
|
131
|
+
static maxRight(rects) {
|
|
132
|
+
return rects.reduce((acc, r) => Math.max(acc, r.right()), 0);
|
|
133
|
+
}
|
|
134
|
+
/** This function gives the largest bottom edge, or 0 for an empty list. */
|
|
135
|
+
static maxBottom(rects) {
|
|
136
|
+
return rects.reduce((acc, r) => Math.max(acc, r.bottom()), 0);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
// src/order.ts
|
|
140
|
+
/** This constant lists each sort order. The first one is the default. */
|
|
141
|
+
const SORT_ORDERS = [
|
|
142
|
+
"height-desc",
|
|
143
|
+
"height-asc",
|
|
144
|
+
"width-desc",
|
|
145
|
+
"area-desc",
|
|
146
|
+
"input"
|
|
147
|
+
];
|
|
148
|
+
/** This function gives the comparator of a sort order. The sort is stable, so `input` keeps the order. */
|
|
149
|
+
function compareRects(order) {
|
|
150
|
+
switch (order) {
|
|
151
|
+
case "height-desc": return (a, b) => b.size.y - a.size.y;
|
|
152
|
+
case "height-asc": return (a, b) => a.size.y - b.size.y;
|
|
153
|
+
case "width-desc": return (a, b) => b.size.x - a.size.x;
|
|
154
|
+
case "area-desc": return (a, b) => b.area() - a.area();
|
|
155
|
+
case "input": return () => 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// src/skyline.ts
|
|
159
|
+
/**
|
|
160
|
+
* This function finds the lowest window of width `rw` on a skyline. For each `x` in `[0, width - rw]`, the height
|
|
161
|
+
* of the window is the largest value of `skyline[x, x + rw)`. The first `x` with the smallest height wins. A
|
|
162
|
+
* monotonic deque makes the search O(width) for one rectangle.
|
|
163
|
+
*/
|
|
164
|
+
function lowestWindow(skyline, width, rw) {
|
|
165
|
+
const deque = new Int32Array(width);
|
|
166
|
+
let head = 0;
|
|
167
|
+
let tail = 0;
|
|
168
|
+
let bestX = 0;
|
|
169
|
+
let bestY = Infinity;
|
|
170
|
+
for (let i = 0; i < width; i++) {
|
|
171
|
+
while (tail > head && skyline[deque[tail - 1]] <= skyline[i]) tail--;
|
|
172
|
+
deque[tail++] = i;
|
|
173
|
+
if (deque[head] <= i - rw) head++;
|
|
174
|
+
if (i >= rw - 1) {
|
|
175
|
+
const max = skyline[deque[head]];
|
|
176
|
+
if (max < bestY) {
|
|
177
|
+
bestY = max;
|
|
178
|
+
bestX = i - rw + 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
x: bestX,
|
|
184
|
+
y: bestY
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* This function packs rectangles into an outer rectangle with a skyline. It puts each rectangle in the lowest place
|
|
189
|
+
* where it fits, and it does not change the width of the outer rectangle.
|
|
190
|
+
*
|
|
191
|
+
* - A `locked` rectangle keeps its position. The skyline starts at its bottom edge across its width, thus the other
|
|
192
|
+
* rectangles go under it. The space above it stays empty, because a skyline has no holes.
|
|
193
|
+
* - The skyline has a cell for each whole unit of the width. A rectangle that needs more cells than the outer
|
|
194
|
+
* rectangle has goes under all other rectangles, at the left edge.
|
|
195
|
+
* - Without `fixedSize`, the height of the outer rectangle grows to the content. With `fixedSize`, a rectangle that
|
|
196
|
+
* does not fit is not packed: its `wasPacked` stays `false`.
|
|
197
|
+
* - The default sort order is `height-desc`. The order of the array of the caller stays.
|
|
198
|
+
*/
|
|
199
|
+
function packSkyline(rects, outer, options = {}) {
|
|
200
|
+
const width = Math.floor(outer.size.x);
|
|
201
|
+
if (width <= 0) return outer;
|
|
202
|
+
const skyline = new Float64Array(width);
|
|
203
|
+
for (const rect of rects) {
|
|
204
|
+
if (!rect.locked) continue;
|
|
205
|
+
rect.wasPacked = true;
|
|
206
|
+
const x0 = Math.max(0, Math.floor(rect.position.x - outer.position.x));
|
|
207
|
+
const x1 = Math.min(width, Math.ceil(rect.position.x - outer.position.x + rect.size.x));
|
|
208
|
+
const bottom = rect.position.y - outer.position.y + rect.size.y;
|
|
209
|
+
for (let i = x0; i < x1; i++) skyline[i] = Math.max(skyline[i], bottom);
|
|
210
|
+
if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, bottom);
|
|
211
|
+
}
|
|
212
|
+
for (const rect of rects.toSorted(compareRects(options.sortOrder ?? "height-desc"))) {
|
|
213
|
+
if (rect.wasPacked) continue;
|
|
214
|
+
const rw = Math.ceil(rect.size.x);
|
|
215
|
+
const rh = rect.size.y;
|
|
216
|
+
if (rw <= 0 || rh <= 0) continue;
|
|
217
|
+
let x;
|
|
218
|
+
let y;
|
|
219
|
+
if (rw > width) {
|
|
220
|
+
if (outer.fixedSize) continue;
|
|
221
|
+
x = 0;
|
|
222
|
+
y = 0;
|
|
223
|
+
for (let i = 0; i < width; i++) if (skyline[i] > y) y = skyline[i];
|
|
224
|
+
} else {
|
|
225
|
+
({x, y} = lowestWindow(skyline, width, rw));
|
|
226
|
+
if (outer.fixedSize && y + rh > outer.size.y) continue;
|
|
227
|
+
}
|
|
228
|
+
rect.position.set(x + outer.position.x, y + outer.position.y);
|
|
229
|
+
rect.wasPacked = true;
|
|
230
|
+
const top = y + rh;
|
|
231
|
+
for (let i = x; i < Math.min(width, x + rw); i++) skyline[i] = top;
|
|
232
|
+
if (!outer.fixedSize) outer.size.y = Math.max(outer.size.y, top);
|
|
233
|
+
}
|
|
234
|
+
return outer;
|
|
235
|
+
}
|
|
236
|
+
// src/pack.ts
|
|
237
|
+
function packInto(rects, region, offset, order) {
|
|
238
|
+
const unpacked = Rect.unpacked(rects);
|
|
239
|
+
pack(unpacked, region, { sortOrder: order });
|
|
240
|
+
for (const r of Rect.packed(unpacked)) r.translateBy(offset);
|
|
241
|
+
}
|
|
242
|
+
function packRow(rects, outer, order) {
|
|
243
|
+
const row = [];
|
|
244
|
+
const byHeight = rects.toSorted(compareRects(order));
|
|
245
|
+
const fillGaps = order !== "input";
|
|
246
|
+
const rowRect = new Rect();
|
|
247
|
+
rowRect.size.set(outer.size.x, 0);
|
|
248
|
+
for (const rect of byHeight) {
|
|
249
|
+
if (rect.wasPacked) continue;
|
|
250
|
+
if (rect.size.x > rowRect.size.x) {
|
|
251
|
+
if (outer.fixedSize && rect.size.x > outer.size.x) continue;
|
|
252
|
+
const outerLeft = new Rect();
|
|
253
|
+
outerLeft.position.setTo(rowRect.position);
|
|
254
|
+
outerLeft.size.set(outer.size.x - rowRect.position.x, rowRect.size.y);
|
|
255
|
+
outerLeft.fixedSize = true;
|
|
256
|
+
if (fillGaps) packInto(rects, outerLeft, outer.position, order);
|
|
257
|
+
rowRect.position.set(0, rowRect.bottom());
|
|
258
|
+
rowRect.size.set(outer.size.x, 0);
|
|
259
|
+
row.length = 0;
|
|
260
|
+
}
|
|
261
|
+
if (rowRect.position.y + rect.size.y > outer.size.y) {
|
|
262
|
+
if (outer.fixedSize) continue;
|
|
263
|
+
outer.size.y += rect.size.y;
|
|
264
|
+
if (!outer.fixedWidth && !outer.size.x) outer.size.x = rect.size.x;
|
|
265
|
+
Rect.resetPacked(rects);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
row.push(rect);
|
|
269
|
+
rect.position.setTo(rowRect.position.add(outer.position));
|
|
270
|
+
rowRect.shrinkLeft(rect.size.x);
|
|
271
|
+
rect.wasPacked = true;
|
|
272
|
+
if (rect.size.y > rowRect.size.y) rowRect.size.y = rect.size.y;
|
|
273
|
+
if (fillGaps && row.length > 1) {
|
|
274
|
+
const rectInRow = row[row.length - 1];
|
|
275
|
+
const outerLeft = new Rect();
|
|
276
|
+
outerLeft.position.set(rectInRow.position.x, rectInRow.bottom());
|
|
277
|
+
outerLeft.size.set(rectInRow.size.x, rowRect.size.y - rectInRow.size.y);
|
|
278
|
+
outerLeft.fixedWidth = true;
|
|
279
|
+
outerLeft.fixedSize = true;
|
|
280
|
+
packInto(rects, outerLeft, outer.position, order);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* This function packs rectangles into an outer rectangle of a given width with a guillotine row packing. It does
|
|
287
|
+
* not change the width. Without `fixedSize`, the height grows until all rectangles fit. A `locked` rectangle keeps
|
|
288
|
+
* its position, but the rows do not go around it. `pack` sets the width first and then uses this function.
|
|
289
|
+
*/
|
|
290
|
+
function packRows(rects, outer, options = {}) {
|
|
291
|
+
const order = options.sortOrder ?? "height-desc";
|
|
292
|
+
for (const r of rects) if (r.locked) r.wasPacked = true;
|
|
293
|
+
while (!packRow(rects, outer, order));
|
|
294
|
+
return outer;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* This function packs rectangles into an outer rectangle with a guillotine row packing. It sets the position
|
|
298
|
+
* of each rectangle and the size of the outer rectangle, and it gives the outer rectangle.
|
|
299
|
+
*
|
|
300
|
+
* - Without fixed sizes, the outer rectangle starts with a width of 1.5 times the square root of the total area.
|
|
301
|
+
* This width is not less than the width of the widest rectangle. The height grows until all rectangles fit.
|
|
302
|
+
* - With `fixedWidth`, the width stays. A rectangle wider than that width goes on its own row and overflows.
|
|
303
|
+
* - With `fixedSize`, a rectangle that does not fit is not packed: its `wasPacked` stays `false`.
|
|
304
|
+
* - `options.strategy` selects `packRows` (the default) or `packSkyline` after the width is set.
|
|
305
|
+
*/
|
|
306
|
+
function pack(rects, outer = new Rect(), options = {}) {
|
|
307
|
+
let totalArea = 0;
|
|
308
|
+
let widest = 0;
|
|
309
|
+
let minX = rects.length > 0 ? Infinity : 0;
|
|
310
|
+
let minY = rects.length > 0 ? Infinity : 0;
|
|
311
|
+
for (const r of rects) {
|
|
312
|
+
totalArea += r.area();
|
|
313
|
+
widest = Math.max(widest, r.size.x);
|
|
314
|
+
minX = Math.min(minX, r.size.x);
|
|
315
|
+
minY = Math.min(minY, r.size.y);
|
|
316
|
+
}
|
|
317
|
+
const idealWidth = outer.fixedWidth ? outer.size.x : Math.max(widest, Math.min(1.5 * Math.sqrt(totalArea), outer.size.x || Infinity));
|
|
318
|
+
const ideal = Vector.of(idealWidth).clamp(new Vector(minX, minY), Vector.Infinity);
|
|
319
|
+
if (!outer.fixedSize) outer.size.setTo(outer.fixedWidth ? new Vector(outer.size.x, ideal.y) : ideal);
|
|
320
|
+
if (options.strategy === "skyline") packSkyline(rects, outer, options);
|
|
321
|
+
else packRows(rects, outer, options);
|
|
322
|
+
if (!outer.fixedSize) {
|
|
323
|
+
const contentRight = Rect.maxRight(rects);
|
|
324
|
+
if (!outer.size.x) outer.size.x = contentRight;
|
|
325
|
+
else if (!outer.fixedWidth) outer.size.x = Math.min(contentRight, outer.size.x);
|
|
326
|
+
outer.size.y = Rect.maxBottom(rects);
|
|
327
|
+
}
|
|
328
|
+
return outer;
|
|
329
|
+
}
|
|
330
|
+
// src/push.ts
|
|
331
|
+
/** This function gives the overlap of two rectangles on each axis. A value of 0 or less is no overlap. */
|
|
332
|
+
function overlapOf(a, b) {
|
|
333
|
+
return {
|
|
334
|
+
x: Math.min(a.right(), b.right()) - Math.max(a.position.x, b.position.x),
|
|
335
|
+
y: Math.min(a.bottom(), b.bottom()) - Math.max(a.position.y, b.position.y)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* This function gives the direction of a first shove. The axis has the smaller overlap. The sign points away from the
|
|
340
|
+
* center of the pusher. When that shove crosses 0, the sign is positive.
|
|
341
|
+
*/
|
|
342
|
+
function firstDirection(pusher, r, gap) {
|
|
343
|
+
const overlap = overlapOf(pusher, r);
|
|
344
|
+
if (overlap.x < overlap.y) return {
|
|
345
|
+
axis: "x",
|
|
346
|
+
sign: r.position.x + r.size.x / 2 < pusher.position.x + pusher.size.x / 2 && pusher.position.x - r.size.x - gap >= 0 ? -1 : 1
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
axis: "y",
|
|
350
|
+
sign: r.position.y + r.size.y / 2 < pusher.position.y + pusher.size.y / 2 && pusher.position.y - r.size.y - gap >= 0 ? -1 : 1
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/** This function puts a rectangle past the pusher, along a direction. The rectangle does not go past 0. */
|
|
354
|
+
function shove(pusher, r, direction, gap) {
|
|
355
|
+
if (direction.axis === "x") {
|
|
356
|
+
const x = direction.sign > 0 ? pusher.right() + gap : pusher.position.x - r.size.x - gap;
|
|
357
|
+
r.position.set(Math.max(0, x), r.position.y);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const y = direction.sign > 0 ? pusher.bottom() + gap : pusher.position.y - r.size.y - gap;
|
|
361
|
+
r.position.set(r.position.x, Math.max(0, y));
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* This function moves each rectangle that a placed rectangle overlaps, and the rectangles that those overlap in turn.
|
|
365
|
+
* A shove takes the axis of the smaller overlap, away from the pusher. A shoved rectangle keeps the direction of its first
|
|
366
|
+
* shove when it shoves the next one, so the cascade goes outward. The placed rectangle and each locked rectangle stay.
|
|
367
|
+
* The function moves the rectangles in place and gives the rectangles that moved.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```ts
|
|
371
|
+
* const moved = pushCascade(dropped, cards, { gap: 8 });
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
function pushCascade(placed, rects, options = {}) {
|
|
375
|
+
const gap = options.gap ?? 8;
|
|
376
|
+
const maxShoves = options.maxShoves ?? rects.length * 8;
|
|
377
|
+
const moved = /* @__PURE__ */ new Set();
|
|
378
|
+
const queue = [{ pusher: placed }];
|
|
379
|
+
let shoves = 0;
|
|
380
|
+
while (shoves < maxShoves) {
|
|
381
|
+
const step = queue.shift();
|
|
382
|
+
if (!step) break;
|
|
383
|
+
const { pusher, direction } = step;
|
|
384
|
+
for (const r of rects) {
|
|
385
|
+
if (r === pusher || r === placed || r.locked) continue;
|
|
386
|
+
const overlap = overlapOf(pusher, r);
|
|
387
|
+
if (overlap.x <= 0 || overlap.y <= 0) continue;
|
|
388
|
+
const next = direction ?? firstDirection(pusher, r, gap);
|
|
389
|
+
shove(pusher, r, next, gap);
|
|
390
|
+
moved.add(r);
|
|
391
|
+
queue.push({
|
|
392
|
+
pusher: r,
|
|
393
|
+
direction: next
|
|
394
|
+
});
|
|
395
|
+
if (++shoves >= maxShoves) break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return [...moved];
|
|
399
|
+
}
|
|
400
|
+
// src/metrics.ts
|
|
401
|
+
/**
|
|
402
|
+
* This function measures a packing. It gives the hull and its aspect, the rows and the raggedness of the right side.
|
|
403
|
+
* It also gives the part of the hull that the rectangles cover, and the widest rectangle against the median width.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* const { utilization, raggedness } = packingMetrics(Rect.packed(cards));
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
function packingMetrics(rects, options = {}) {
|
|
411
|
+
const tolerance = options.rowTolerance ?? 40;
|
|
412
|
+
const boxes = rects.filter((r) => r.size.x > 0 && r.size.y > 0);
|
|
413
|
+
if (boxes.length === 0) return {
|
|
414
|
+
count: 0,
|
|
415
|
+
hull: {
|
|
416
|
+
width: 0,
|
|
417
|
+
height: 0
|
|
418
|
+
},
|
|
419
|
+
hullAspect: 0,
|
|
420
|
+
rows: [],
|
|
421
|
+
raggedness: 0,
|
|
422
|
+
utilization: 0,
|
|
423
|
+
widestOverMedian: 0
|
|
424
|
+
};
|
|
425
|
+
const left = Math.min(...boxes.map((r) => r.position.x));
|
|
426
|
+
const top = Math.min(...boxes.map((r) => r.position.y));
|
|
427
|
+
const right = Math.max(...boxes.map((r) => r.right()));
|
|
428
|
+
const bottom = Math.max(...boxes.map((r) => r.bottom()));
|
|
429
|
+
const hull = {
|
|
430
|
+
width: right - left,
|
|
431
|
+
height: bottom - top
|
|
432
|
+
};
|
|
433
|
+
const rows = [];
|
|
434
|
+
for (const r of boxes.toSorted((a, b) => a.position.y - b.position.y)) {
|
|
435
|
+
const row = rows.at(-1);
|
|
436
|
+
if (row && Math.abs(r.position.y - row.top) <= tolerance) {
|
|
437
|
+
row.right = Math.max(row.right, r.right());
|
|
438
|
+
row.count += 1;
|
|
439
|
+
} else rows.push({
|
|
440
|
+
top: r.position.y,
|
|
441
|
+
right: r.right(),
|
|
442
|
+
count: 1
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
const rights = rows.map((row) => row.right);
|
|
446
|
+
const widths = boxes.map((r) => r.size.x).toSorted((a, b) => a - b);
|
|
447
|
+
const median = widths[Math.floor(widths.length / 2)] ?? 0;
|
|
448
|
+
const widest = widths.at(-1) ?? 0;
|
|
449
|
+
const area = boxes.reduce((sum, r) => sum + r.area(), 0);
|
|
450
|
+
return {
|
|
451
|
+
count: boxes.length,
|
|
452
|
+
hull,
|
|
453
|
+
hullAspect: hull.height > 0 ? hull.width / hull.height : 0,
|
|
454
|
+
rows: rows.map((row) => row.count),
|
|
455
|
+
raggedness: Math.max(...rights) - Math.min(...rights),
|
|
456
|
+
utilization: hull.width * hull.height > 0 ? area / (hull.width * hull.height) : 0,
|
|
457
|
+
widestOverMedian: median > 0 ? widest / median : 0
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
export { Rect, SORT_ORDERS, Vector, compareRects, lowestWindow, pack, packRows, packSkyline, packingMetrics, pushCascade };
|
|
461
|
+
|
|
462
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/vector.ts","../src/rect.ts","../src/order.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/**\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 { 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 /** The packing: `rows` (the default) or `skyline`. Use `skyline` to pack around locked rectangles. */\n strategy?: \"rows\" | \"skyline\";\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) or `packSkyline` 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 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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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,64 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package",
|
|
2
3
|
"name": "@mark1russell7/packing",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Rectangle packing: shelf, guillotine and skyline strategies, push cascades and measures of a layout.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"rectangle-packing",
|
|
8
|
+
"bin-packing",
|
|
9
|
+
"strip-packing",
|
|
10
|
+
"layout",
|
|
11
|
+
"guillotine",
|
|
12
|
+
"skyline",
|
|
13
|
+
"typescript"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://mark1russell7.github.io/packing/",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/mark1russell7/packing/issues"
|
|
18
|
+
},
|
|
5
19
|
"repository": {
|
|
6
20
|
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/mark1russell7/packing.git"
|
|
21
|
+
"url": "git+https://github.com/mark1russell7/packing.git",
|
|
22
|
+
"directory": "packages/packing"
|
|
8
23
|
},
|
|
9
24
|
"license": "MIT",
|
|
25
|
+
"author": "Mark Russell (https://github.com/mark1russell7)",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"main": "./build/index.js",
|
|
29
|
+
"types": "./build/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./build/index.d.ts",
|
|
33
|
+
"default": "./build/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"build",
|
|
39
|
+
"src",
|
|
40
|
+
"!src/**/*.test.ts",
|
|
41
|
+
"!src/**/*.test.tsx",
|
|
42
|
+
"!src/main.tsx",
|
|
43
|
+
"!src/page.css"
|
|
44
|
+
],
|
|
10
45
|
"publishConfig": {
|
|
11
46
|
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22.12"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@mark1russell7/cue": "^1.0.2",
|
|
53
|
+
"@types/node": "^26.6.4",
|
|
54
|
+
"tsdown": "^0.23.1",
|
|
55
|
+
"typescript": "^7.0.2",
|
|
56
|
+
"vitest": "^5.0.3"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsdown",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json"
|
|
12
63
|
}
|
|
13
|
-
}
|
|
64
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Vector } from "./vector.ts";
|
|
2
|
+
export { Rect } from "./rect.ts";
|
|
3
|
+
export { pack, packRows, type PackOptions, type PackLayoutOptions } from "./pack.ts";
|
|
4
|
+
export { packSkyline, lowestWindow } from "./skyline.ts";
|
|
5
|
+
export { compareRects, SORT_ORDERS, type SortOrder } from "./order.ts";
|
|
6
|
+
export { pushCascade, type PushOptions } from "./push.ts";
|
|
7
|
+
export { packingMetrics, type MetricsOptions, type PackingMetrics } from "./metrics.ts";
|