@mark1russell7/packing 0.2.1 → 0.2.3

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/src/pack.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { clearMoves, countMove, MOVES } from "./moves.ts";
1
2
  import { compareRects, type SortOrder } from "./order.ts";
2
3
  import { Rect } from "./rect.ts";
3
4
  import { packRowsInSpace, packShelf } from "./shelf.ts";
@@ -25,10 +26,20 @@ export interface PackLayoutOptions extends PackOptions {
25
26
  strategy?: "rows" | "skyline" | "shelf";
26
27
  }
27
28
 
29
+ /** The depth of the calls of `packInto`. Only the outer gap fill counts its rectangles as moves. */
30
+ let fillDepth = 0;
31
+
28
32
  function packInto<T>(rects: Rect<T>[], region: Rect<T>, offset: Vector, order: SortOrder): void {
29
33
  const unpacked = Rect.unpacked(rects);
30
- pack(unpacked, region, { sortOrder: order });
31
- for (const r of Rect.packed(unpacked)) r.translateBy(offset);
34
+ fillDepth++;
35
+ try {
36
+ pack(unpacked, region, { sortOrder: order });
37
+ } finally {
38
+ fillDepth--;
39
+ }
40
+ const placed = Rect.packed(unpacked);
41
+ if (fillDepth === 0) countMove(MOVES.rows, placed.length);
42
+ for (const r of placed) r.translateBy(offset);
32
43
  }
33
44
 
34
45
  function packRow<T>(rects: Rect<T>[], outer: Rect<T>, order: SortOrder): boolean {
@@ -100,7 +111,8 @@ export function packRows<T>(rects: Rect<T>[], outer: Rect<T>, options: PackOptio
100
111
  const space = options.space;
101
112
  if (space !== undefined && (!space.plain || rects.some((r) => r.locked))) return packRowsInSpace(rects, outer, order, space);
102
113
  for (const r of rects) if (r.locked) r.wasPacked = true;
103
- while (!packRow(rects, outer, order));
114
+ // A row that does not fit grows the height and starts again, thus the gap fills of the try before do not count
115
+ while (!packRow(rects, outer, order)) if (fillDepth === 0) clearMoves(MOVES.rows);
104
116
  return outer;
105
117
  }
106
118
 
package/src/shelf.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { countMove, MOVES } from "./moves.ts";
1
2
  import { compareRects, type SortOrder } from "./order.ts";
2
3
  import type { Rect } from "./rect.ts";
3
4
  import { firstFit, type FreeSpace, type FreeSpan } from "./space.ts";
@@ -111,6 +112,8 @@ export function packShelf<T>(rects: Rect<T>[], outer: Rect<T>, options: ShelfOpt
111
112
 
112
113
  if (outer.fixedSize && rowTop + h > outer.size.y) continue;
113
114
 
115
+ // The move of a shelf: the rectangle joins an open shelf that a taller rectangle made
116
+ if (!rowEmpty && rowTop + h < rowBottom) countMove(MOVES.shelf);
114
117
  rect.position.set(x + outer.position.x, rowTop + outer.position.y);
115
118
  rect.wasPacked = true;
116
119
  rowEmpty = false;
package/src/skyline.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { countMove, MOVES } from "./moves.ts";
1
2
  import { compareRects, type SortOrder } from "./order.ts";
2
3
  import type { Rect } from "./rect.ts";
3
4
  import type { FreeSpace } from "./space.ts";
@@ -11,6 +12,19 @@ import type { FreeSpace } from "./space.ts";
11
12
  * `limit[x, x + rw)`. A second deque keeps that smallest value. No valid window gives the height `Infinity`.
12
13
  */
13
14
  export function lowestWindow(skyline: ArrayLike<number>, width: number, rw: number, limit?: ArrayLike<number>, rh = 0): { x: number; y: number } {
15
+ const { x, y } = findWindow(skyline, width, rw, limit, rh);
16
+ return { x, y };
17
+ }
18
+
19
+ /** The lowest window of `findWindow`, and `first`: the x of the leftmost valid window, or `-1` */
20
+ interface Window {
21
+ x: number;
22
+ y: number;
23
+ first: number;
24
+ }
25
+
26
+ /** This function is `lowestWindow`, and it also gives the x of the leftmost valid window. */
27
+ function findWindow(skyline: ArrayLike<number>, width: number, rw: number, limit?: ArrayLike<number>, rh = 0): Window {
14
28
  if (limit !== undefined) return lowestLimitedWindow(skyline, limit, width, rw, rh);
15
29
  const deque = new Int32Array(width);
16
30
  let head = 0;
@@ -29,11 +43,16 @@ export function lowestWindow(skyline: ArrayLike<number>, width: number, rw: numb
29
43
  }
30
44
  }
31
45
  }
32
- return { x: bestX, y: bestY };
46
+ return { x: bestX, y: bestY, first: width >= rw ? 0 : -1 };
47
+ }
48
+
49
+ /** This function counts the move of the skyline: a window that is lower than the leftmost valid window. */
50
+ function countWindow(w: Window): void {
51
+ if (Number.isFinite(w.y) && w.first >= 0 && w.x !== w.first) countMove(MOVES.skyline);
33
52
  }
34
53
 
35
54
  /** This function is `lowestWindow` with a limit for each cell. */
36
- function lowestLimitedWindow(skyline: ArrayLike<number>, limit: ArrayLike<number>, width: number, rw: number, rh: number): { x: number; y: number } {
55
+ function lowestLimitedWindow(skyline: ArrayLike<number>, limit: ArrayLike<number>, width: number, rw: number, rh: number): Window {
37
56
  const high = new Int32Array(width);
38
57
  const low = new Int32Array(width);
39
58
  let hh = 0;
@@ -42,6 +61,7 @@ function lowestLimitedWindow(skyline: ArrayLike<number>, limit: ArrayLike<number
42
61
  let lt = 0;
43
62
  let bestX = 0;
44
63
  let bestY = Infinity;
64
+ let first = -1;
45
65
  for (let i = 0; i < width; i++) {
46
66
  while (ht > hh && skyline[high[ht - 1]!]! <= skyline[i]!) ht--;
47
67
  high[ht++] = i;
@@ -51,13 +71,15 @@ function lowestLimitedWindow(skyline: ArrayLike<number>, limit: ArrayLike<number
51
71
  if (low[lh]! <= i - rw) lh++;
52
72
  if (i >= rw - 1) {
53
73
  const max = skyline[high[hh]!]!;
54
- if (max < bestY && max + rh <= limit[low[lh]!]!) {
74
+ const valid = max + rh <= limit[low[lh]!]!;
75
+ if (valid && first < 0) first = i - rw + 1;
76
+ if (max < bestY && valid) {
55
77
  bestY = max;
56
78
  bestX = i - rw + 1;
57
79
  }
58
80
  }
59
81
  }
60
- return { x: bestX, y: bestY };
82
+ return { x: bestX, y: bestY, first };
61
83
  }
62
84
 
63
85
  /** The options of `packSkyline` */
@@ -116,8 +138,10 @@ export function packSkyline<T>(rects: Rect<T>[], outer: Rect<T>, options: Skylin
116
138
  y = 0;
117
139
  for (let i = 0; i < width; i++) if (skyline[i]! > y) y = skyline[i]!;
118
140
  } else {
119
- ({ x, y } = lowestWindow(skyline, width, rw));
141
+ const found = findWindow(skyline, width, rw);
142
+ ({ x, y } = found);
120
143
  if (outer.fixedSize && y + rh > outer.size.y) continue;
144
+ countWindow(found);
121
145
  }
122
146
 
123
147
  rect.position.set(x + outer.position.x, y + outer.position.y);
@@ -179,7 +203,8 @@ function packSkylineInSpace<T>(rects: Rect<T>[], outer: Rect<T>, order: SortOrde
179
203
  outer.size.y = Math.max(outer.size.y, y + rh);
180
204
  continue;
181
205
  }
182
- ({ x, y } = lowestWindow(skyline, width, rw, limit, rh));
206
+ let found = findWindow(skyline, width, rw, limit, rh);
207
+ ({ x, y } = found);
183
208
  let sky: Float64Array = skyline;
184
209
  let lim: Float64Array = limit;
185
210
  if (!Number.isFinite(y)) {
@@ -189,10 +214,12 @@ function packSkylineInSpace<T>(rects: Rect<T>[], outer: Rect<T>, order: SortOrde
189
214
  for (let i = 0; i < width; i++) {
190
215
  while (Number.isFinite(lim[i]!) && lim[i]! - sky[i]! < rh && lim[i]! < end) [sky[i], lim[i]] = rangeOf(i, lim[i]!);
191
216
  }
192
- ({ x, y } = lowestWindow(sky, width, rw, lim, rh));
217
+ found = findWindow(sky, width, rw, lim, rh);
218
+ ({ x, y } = found);
193
219
  if (!Number.isFinite(y)) continue;
194
220
  }
195
221
 
222
+ countWindow(found);
196
223
  rect.position.set(x + ox, y + oy);
197
224
  rect.wasPacked = true;
198
225
  const top = y + rh;