@mark1russell7/packing 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -2
- package/build/index.d.ts +146 -31
- package/build/index.d.ts.map +1 -1
- package/build/index.js +725 -42
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters.ts +110 -23
- package/src/frame.ts +37 -10
- package/src/index.ts +13 -2
- package/src/maxrects.ts +367 -0
- package/src/measures.ts +18 -7
- package/src/moves.ts +32 -3
- package/src/pack.ts +17 -5
- package/src/registry.ts +2 -2
- package/src/shelf.ts +200 -0
- package/src/space.ts +36 -1
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.2.
|
|
4
|
+
"version": "0.2.5",
|
|
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/adapters.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// The adapters of 0.1 (docs/PACKER.md): each function of 0.1 runs behind the Packer interface as a strategy.
|
|
2
2
|
|
|
3
3
|
import { EPS, type PackInput, type PackOutput } from "./contract.ts";
|
|
4
|
-
import { boundsOf, buildFrame, now, outerOf, readFrame, rectsOf, viaAxes, type Entry } from "./frame.ts";
|
|
4
|
+
import { boundsOf, buildFrame, frameTurn, now, outerOf, readFrame, rectsOf, viaAxes, type Entry, type Frame, type FrameTurns } from "./frame.ts";
|
|
5
5
|
import { packFree } from "./free.ts";
|
|
6
|
+
import type { MaxRectsRule } from "./maxrects.ts";
|
|
6
7
|
import { clearMoves, moveCount, MOVES } from "./moves.ts";
|
|
7
8
|
import type { SortOrder } from "./order.ts";
|
|
8
|
-
import { pack } from "./pack.ts";
|
|
9
|
+
import { pack, type PackLayoutOptions } from "./pack.ts";
|
|
9
10
|
import { pushCascade } from "./push.ts";
|
|
10
11
|
import { Rect } from "./rect.ts";
|
|
12
|
+
import type { ShelfFit } from "./shelf.ts";
|
|
11
13
|
import { packSkyline } from "./skyline.ts";
|
|
12
14
|
import { spaceOf } from "./space.ts";
|
|
13
15
|
import { assertSupported, cellOf, UnsupportedCell, type PackContext, type SizeSearch, type Strategy, type Supports } from "./strategy.ts";
|
|
@@ -18,8 +20,35 @@ export interface LayoutStrategyOptions {
|
|
|
18
20
|
sortOrder: SortOrder;
|
|
19
21
|
/** The number of widths that the first pass of the size search `log-sweep` tries. */
|
|
20
22
|
samples: number;
|
|
23
|
+
/**
|
|
24
|
+
* The rule of `shelf` to choose a shelf: `next`, `first` or `best`. Only `shelf` reads it. Without it, `shelf` packs
|
|
25
|
+
* as in 0.2.3. With it, the trace also counts `move.firstFit` and `move.bestFit`.
|
|
26
|
+
*/
|
|
27
|
+
fit?: ShelfFit;
|
|
21
28
|
}
|
|
22
29
|
|
|
30
|
+
/** The options of the strategy `maxrects`. The strategy does not read `fit`. */
|
|
31
|
+
export interface MaxRectsStrategyOptions extends LayoutStrategyOptions {
|
|
32
|
+
/** The rule to choose a free rectangle: `bssf` (the default), `blsf`, `baf`, `bl` or `cp`. `MAXRECTS_RULES` tells each rule. */
|
|
33
|
+
rule: MaxRectsRule;
|
|
34
|
+
/** The value `true` lets each turnable item that is not locked turn by 90 degrees. The default is `false`. */
|
|
35
|
+
rotate: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The largest height of a band of the approximation of a container that is not a plain rectangle. `MaxRectsOptions`
|
|
38
|
+
* tells the default.
|
|
39
|
+
*/
|
|
40
|
+
bandHeight?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The id of a strategy of `pack`. */
|
|
44
|
+
type LayoutId = "rows" | "skyline" | "shelf" | "maxrects";
|
|
45
|
+
|
|
46
|
+
/** The options of a strategy of `pack`: the options of `maxrects` are optional for the other strategies. */
|
|
47
|
+
type AnyLayoutOptions = LayoutStrategyOptions & Partial<Pick<MaxRectsStrategyOptions, "rule" | "rotate" | "bandHeight">>;
|
|
48
|
+
|
|
49
|
+
/** The options of `pack` that a strategy adds to the sort order and the space. */
|
|
50
|
+
type PackExtras = Pick<PackLayoutOptions, "fit" | "rule" | "bandHeight" | "turnable" | "turned">;
|
|
51
|
+
|
|
23
52
|
/** The options of the strategy `push`. */
|
|
24
53
|
export interface PushStrategyOptions {
|
|
25
54
|
/** The order of the items that the cascade does not place. */
|
|
@@ -55,37 +84,73 @@ function supportsOf(cells: { [D in keyof Supports]: readonly Supports[D][number]
|
|
|
55
84
|
};
|
|
56
85
|
}
|
|
57
86
|
|
|
87
|
+
/**
|
|
88
|
+
* This function gives the options of `pack` that a strategy adds: the fit of `shelf`, or the rule, the bands and the turns
|
|
89
|
+
* of `maxrects`. A turnable item turns only with `rotate`, and a locked item does not turn.
|
|
90
|
+
*/
|
|
91
|
+
function extrasOf(id: LayoutId, options: AnyLayoutOptions, frame: Frame): PackExtras {
|
|
92
|
+
if (id === "shelf") return options.fit !== undefined ? { fit: options.fit } : {};
|
|
93
|
+
if (id !== "maxrects") return {};
|
|
94
|
+
const out: PackExtras = { rule: options.rule ?? "bssf", ...(options.bandHeight === undefined ? {} : { bandHeight: options.bandHeight }) };
|
|
95
|
+
if (options.rotate !== true) return out;
|
|
96
|
+
const turnable = new Set(frame.entries.filter((e) => e.item?.turnable === true && !e.rect.locked).map((e) => e.rect));
|
|
97
|
+
return { ...out, turnable, turned: new Set() };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* This function gives the move counts of the trace of a strategy: its own move, and the moves of a shelf fit. The trace of
|
|
102
|
+
* `maxrects` has the move of its rule and `move.split`. The rule `bl` has only `move.split`.
|
|
103
|
+
*/
|
|
104
|
+
function movesOf(id: LayoutId, extras: PackExtras): Record<string, number> {
|
|
105
|
+
if (id === "maxrects") {
|
|
106
|
+
const rule = extras.rule ?? "bssf";
|
|
107
|
+
const out: Record<string, number> = rule === "bl" ? {} : { [MOVES[rule]]: moveCount(MOVES[rule]) };
|
|
108
|
+
out[MOVES.split] = moveCount(MOVES.split);
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
const out: Record<string, number> = { [MOVES[id]]: moveCount(MOVES[id]) };
|
|
112
|
+
if (extras.fit !== undefined) for (const move of [MOVES.firstFit, MOVES.bestFit]) out[move] = moveCount(move);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** This function gives the turns of a packing for `readFrame`, or `undefined` when no rectangle can turn. */
|
|
117
|
+
function turnsOf(extras: PackExtras, transposed: boolean): FrameTurns | undefined {
|
|
118
|
+
return extras.turned === undefined ? undefined : { rects: extras.turned, turn: frameTurn(transposed) };
|
|
119
|
+
}
|
|
120
|
+
|
|
58
121
|
/** This function packs an input with a fixed size or a fixed width with one strategy of `pack`. */
|
|
59
|
-
function packFixedWidth(id:
|
|
122
|
+
function packFixedWidth(id: LayoutId, input: PackInput, options: AnyLayoutOptions, transposed = false): PackOutput {
|
|
60
123
|
const start = now();
|
|
61
|
-
const frame = buildFrame(input);
|
|
124
|
+
const frame = buildFrame(input, false, id === "maxrects" && options.rotate === true);
|
|
62
125
|
const rects = rectsOf(frame);
|
|
126
|
+
const extras = extrasOf(id, options, frame);
|
|
63
127
|
clearMoves();
|
|
64
|
-
if (frame.w > 0) pack(rects, outerOf(frame), { strategy: id, sortOrder: options.sortOrder, space: spaceOf(input, frame) });
|
|
65
|
-
const output = readFrame(input, frame, []);
|
|
66
|
-
return { ...output, trace: [{ id, ms: now() - start, measures: { "pack.overflow": output.overflow.length,
|
|
128
|
+
if (frame.w > 0) pack(rects, outerOf(frame), { strategy: id, sortOrder: options.sortOrder, space: spaceOf(input, frame), ...extras });
|
|
129
|
+
const output = readFrame(input, frame, [], [], turnsOf(extras, transposed));
|
|
130
|
+
return { ...output, trace: [{ id, ms: now() - start, measures: { "pack.overflow": output.overflow.length, ...movesOf(id, extras) } }] };
|
|
67
131
|
}
|
|
68
132
|
|
|
69
133
|
/** This function makes the strategy of one function of `pack`. */
|
|
70
|
-
function layoutStrategy(
|
|
71
|
-
id:
|
|
134
|
+
function layoutStrategy<O extends AnyLayoutOptions>(
|
|
135
|
+
id: LayoutId,
|
|
72
136
|
family: string,
|
|
73
137
|
catalog: string[],
|
|
74
|
-
cells: Pick<Supports, "obstacleMove" | "order"
|
|
75
|
-
|
|
76
|
-
|
|
138
|
+
cells: Pick<Supports, "obstacleMove" | "order"> & Partial<Pick<Supports, "rotation">>,
|
|
139
|
+
defaults: O,
|
|
140
|
+
): Strategy<O> {
|
|
141
|
+
const self: Strategy<O> = {
|
|
77
142
|
id,
|
|
78
143
|
family,
|
|
79
144
|
catalog,
|
|
80
145
|
supports: supportsOf({ ...LAYOUT_CELLS, ...cells }),
|
|
81
|
-
defaults: {
|
|
146
|
+
defaults: { ...defaults },
|
|
82
147
|
deterministic: true,
|
|
83
148
|
pack(input, options, context) {
|
|
84
149
|
assertSupported(self.supports, input);
|
|
85
150
|
const o = { ...self.defaults, ...options };
|
|
86
151
|
switch (input.container.fixed) {
|
|
87
152
|
case "height":
|
|
88
|
-
return viaAxes(input, context.previous, (turned) => packFixedWidth(id, turned, o));
|
|
153
|
+
return viaAxes(input, context.previous, (turned) => packFixedWidth(id, turned, o, true));
|
|
89
154
|
case "none":
|
|
90
155
|
return logSweep.search(input, self, context, o);
|
|
91
156
|
default:
|
|
@@ -96,6 +161,9 @@ function layoutStrategy(
|
|
|
96
161
|
return self;
|
|
97
162
|
}
|
|
98
163
|
|
|
164
|
+
/** The defaults of `rows`, `skyline` and `shelf`. */
|
|
165
|
+
const LAYOUT_DEFAULTS: LayoutStrategyOptions = { sortOrder: "height-desc", samples: 48 };
|
|
166
|
+
|
|
99
167
|
/**
|
|
100
168
|
* The strategy `rows` uses `packRows`: a guillotine row packing with a recursive gap fill. With obstacles, locked items or a
|
|
101
169
|
* container shape, the rows go around the holes of the free space and lose the gap fill. Turnable items, groups and item
|
|
@@ -104,7 +172,7 @@ function layoutStrategy(
|
|
|
104
172
|
export const rows: Strategy<LayoutStrategyOptions> = layoutStrategy("rows", "shelf", ["rows-gap-fill", "rows-input"], {
|
|
105
173
|
obstacleMove: ["fixed"],
|
|
106
174
|
order: ["free", "soft", "hard"],
|
|
107
|
-
});
|
|
175
|
+
}, LAYOUT_DEFAULTS);
|
|
108
176
|
|
|
109
177
|
/**
|
|
110
178
|
* The strategy `skyline` uses `packSkyline`: each item goes to the lowest place where it fits. It packs around the
|
|
@@ -113,30 +181,48 @@ export const rows: Strategy<LayoutStrategyOptions> = layoutStrategy("rows", "she
|
|
|
113
181
|
export const skyline: Strategy<LayoutStrategyOptions> = layoutStrategy("skyline", "skyline", ["skyline-bl"], {
|
|
114
182
|
obstacleMove: ["fixed"],
|
|
115
183
|
order: ["free", "soft"],
|
|
116
|
-
});
|
|
184
|
+
}, LAYOUT_DEFAULTS);
|
|
117
185
|
|
|
118
186
|
/**
|
|
119
187
|
* The strategy `shelf` uses `packShelf`: rows filled left to right, around the locked items and the fixed obstacles.
|
|
120
188
|
* With the sort order `input`, the rows keep the rank order. Turnable items, groups and item shapes throw `UnsupportedCell`.
|
|
189
|
+
* The option `fit` selects a shelf rule of roadmap item k21: `next`, `first` or `best`. The default has no `fit`.
|
|
121
190
|
*/
|
|
122
191
|
export const shelf: Strategy<LayoutStrategyOptions> = layoutStrategy("shelf", "shelf", ["shelf-nf", "shelf-nfdh"], {
|
|
123
192
|
obstacleMove: ["fixed"],
|
|
124
193
|
order: ["free", "soft", "hard"],
|
|
125
|
-
});
|
|
194
|
+
}, LAYOUT_DEFAULTS);
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The strategy `maxrects` uses `packMaxRects`: the MaxRects packer of roadmap item k24. It keeps the list of the maximal
|
|
198
|
+
* free rectangles, and the option `rule` chooses the free rectangle for each item. The default rule is `bssf`.
|
|
199
|
+
*
|
|
200
|
+
* - The free rectangles go around the locked items and the fixed obstacles. A plain rectangle container gives exact free
|
|
201
|
+
* rectangles. Another shape gives an inner approximation with horizontal bands.
|
|
202
|
+
* - With `rotate`, each turnable item that is not locked can turn by 90 degrees. Groups and item shapes throw `UnsupportedCell`.
|
|
203
|
+
*/
|
|
204
|
+
export const maxrects: Strategy<MaxRectsStrategyOptions> = layoutStrategy(
|
|
205
|
+
"maxrects",
|
|
206
|
+
"maxrects",
|
|
207
|
+
["maxrects-bssf", "maxrects-blsf", "maxrects-baf", "maxrects-bl", "maxrects-cp", "maxrects-locked"],
|
|
208
|
+
{ obstacleMove: ["fixed"], order: ["free", "soft"], rotation: ["none", "quarter"] },
|
|
209
|
+
{ ...LAYOUT_DEFAULTS, rule: "bssf", rotate: false },
|
|
210
|
+
);
|
|
126
211
|
|
|
127
212
|
/**
|
|
128
213
|
* The size search `log-sweep` uses `packFree` of 0.1.2: widths on a log scale, then the widths near the best width. It
|
|
129
|
-
* wraps `rows`, `skyline` and `
|
|
214
|
+
* wraps `rows`, `skyline`, `shelf` and `maxrects`. The objective is the area of the frame times `1 + aspectWeight * aspectError`.
|
|
130
215
|
*/
|
|
131
216
|
export const logSweep: SizeSearch = {
|
|
132
217
|
id: "log-sweep",
|
|
133
218
|
search(input: PackInput, inner: Strategy, context: PackContext, options?: unknown): PackOutput {
|
|
134
219
|
if (input.container.fixed !== "none") return inner.pack(input, options ?? inner.defaults, context);
|
|
135
220
|
const id = inner.id;
|
|
136
|
-
if (id !== "rows" && id !== "skyline" && id !== "shelf") throw new UnsupportedCell("containerSize", cellOf(input).containerSize);
|
|
137
|
-
const o = { ...(inner.defaults as
|
|
221
|
+
if (id !== "rows" && id !== "skyline" && id !== "shelf" && id !== "maxrects") throw new UnsupportedCell("containerSize", cellOf(input).containerSize);
|
|
222
|
+
const o = { ...(inner.defaults as AnyLayoutOptions), ...(options as Partial<AnyLayoutOptions> | undefined) };
|
|
138
223
|
const start = now();
|
|
139
|
-
const frame = buildFrame(input);
|
|
224
|
+
const frame = buildFrame(input, false, id === "maxrects" && o.rotate === true);
|
|
225
|
+
const extras = extrasOf(id, o, frame);
|
|
140
226
|
const result = packFree(rectsOf(frame), new Rect<number>(), {
|
|
141
227
|
strategy: id,
|
|
142
228
|
sortOrder: o.sortOrder,
|
|
@@ -144,15 +230,16 @@ export const logSweep: SizeSearch = {
|
|
|
144
230
|
samples: o.samples,
|
|
145
231
|
aspect: input.container.aspect ?? 1,
|
|
146
232
|
aspectWeight: input.container.aspectWeight ?? 1,
|
|
233
|
+
...extras,
|
|
147
234
|
});
|
|
148
235
|
const ms = now() - start;
|
|
149
236
|
const runs = result.tried.length + 1;
|
|
150
|
-
const output = readFrame(input, frame, []);
|
|
237
|
+
const output = readFrame(input, frame, [], [], turnsOf(extras, false));
|
|
151
238
|
return {
|
|
152
239
|
...output,
|
|
153
240
|
trace: [
|
|
154
241
|
{ id: "log-sweep", ms, measures: { "pack.search.widths": result.tried.length, "pack.search.ms": ms } },
|
|
155
|
-
{ id, ms: ms / runs, measures: { "pack.overflow": output.overflow.length,
|
|
242
|
+
{ id, ms: ms / runs, measures: { "pack.overflow": output.overflow.length, ...movesOf(id, extras) } },
|
|
156
243
|
],
|
|
157
244
|
};
|
|
158
245
|
},
|
package/src/frame.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// are `mA + gap + mB` apart. The area of the frame is the container less the inset, grown by half the gap on each side.
|
|
4
4
|
|
|
5
5
|
import { boundsOf } from "@mark1russell7/layout-contract";
|
|
6
|
-
import { EPS, marginOf, type Item, type Obstacle, type PackInput, type PackOutput, type Placement, type Region, type StageTrace } from "./contract.ts";
|
|
6
|
+
import { EPS, marginOf, turnItem, type Item, type Obstacle, type PackInput, type PackOutput, type Placement, type Region, type StageTrace } from "./contract.ts";
|
|
7
7
|
import { Rect } from "./rect.ts";
|
|
8
8
|
|
|
9
9
|
const clock = (globalThis as { performance?: { now(): number } }).performance;
|
|
@@ -73,9 +73,10 @@ export function byRank(items: readonly Item[]): Item[] {
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* This function makes the frame of an input. A locked item with a position and an obstacle without `move` are locked
|
|
76
|
-
* rectangles. An obstacle with `move` is a free rectangle only when `movable` is `true`.
|
|
76
|
+
* rectangles. An obstacle with `move` is a free rectangle only when `movable` is `true`. With `turnable`, a turnable item
|
|
77
|
+
* is too large only when it is too large in the two directions.
|
|
77
78
|
*/
|
|
78
|
-
export function buildFrame(input: PackInput, movable = false): Frame {
|
|
79
|
+
export function buildFrame(input: PackInput, movable = false, turnable = false): Frame {
|
|
79
80
|
const bounds = containerBounds(input);
|
|
80
81
|
const inset = input.container.inset ?? 0;
|
|
81
82
|
const half = (input.gap ?? 0) / 2;
|
|
@@ -95,7 +96,10 @@ export function buildFrame(input: PackInput, movable = false): Frame {
|
|
|
95
96
|
if (item.locked === true && item.x !== undefined && item.y !== undefined) {
|
|
96
97
|
rect.locked = true;
|
|
97
98
|
rect.position.set(item.x - dx - ox, item.y - dy - oy);
|
|
98
|
-
} else if (
|
|
99
|
+
} else if (
|
|
100
|
+
(rect.size.x > w + EPS || rect.size.y > h + EPS) &&
|
|
101
|
+
!(turnable && item.turnable === true && rect.size.y <= w + EPS && rect.size.x <= h + EPS)
|
|
102
|
+
) {
|
|
99
103
|
tooLarge.push(item.id);
|
|
100
104
|
continue;
|
|
101
105
|
}
|
|
@@ -128,33 +132,56 @@ export function rectsOf(frame: Frame): Rect<number>[] {
|
|
|
128
132
|
return frame.entries.map((e) => e.rect);
|
|
129
133
|
}
|
|
130
134
|
|
|
135
|
+
/** The turns of a packing: the rectangles that turned, and the turn of an item in the axes of the frame. */
|
|
136
|
+
export interface FrameTurns {
|
|
137
|
+
/** The rectangles of the frame that the packer turned. */
|
|
138
|
+
rects: ReadonlySet<unknown>;
|
|
139
|
+
/** This function gives an item after a turn of 90 degrees clockwise in the axes of the input, in the axes of the frame. */
|
|
140
|
+
turn: (item: Item) => Item;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* This function gives the turn of an item for a frame. A frame of `viaAxes` has the axes changed. The turn of the input
|
|
145
|
+
* goes through that change, thus the item turns the other way in the frame.
|
|
146
|
+
*/
|
|
147
|
+
export function frameTurn(transposed: boolean): (item: Item) => Item {
|
|
148
|
+
return transposed ? (item) => transposeItem(turnItem(transposeItem(item))) : turnItem;
|
|
149
|
+
}
|
|
150
|
+
|
|
131
151
|
/**
|
|
132
152
|
* This function reads the output from the rectangles of a frame after a packing of 0.1. A locked item keeps its
|
|
133
|
-
* position exactly. Each other item that the packer did not place goes to the overflow.
|
|
153
|
+
* position exactly. Each other item that the packer did not place goes to the overflow. With `turns`, each rectangle
|
|
154
|
+
* that turned gives a placement with `turn: 90`.
|
|
134
155
|
*/
|
|
135
|
-
export function readFrame(input: PackInput, frame: Frame, trace: readonly StageTrace[], moved: PackOutput["moved"] = []): PackOutput {
|
|
156
|
+
export function readFrame(input: PackInput, frame: Frame, trace: readonly StageTrace[], moved: PackOutput["moved"] = [], turns?: FrameTurns): PackOutput {
|
|
136
157
|
const placements: Placement[] = [];
|
|
137
158
|
const overflow: string[] = [...frame.tooLarge];
|
|
138
159
|
for (const e of frame.entries) {
|
|
139
160
|
const item = e.item;
|
|
140
161
|
if (item === undefined) continue;
|
|
141
162
|
if (item.locked === true && item.x !== undefined && item.y !== undefined) placements.push({ id: item.id, x: item.x, y: item.y });
|
|
142
|
-
else if (e.rect.wasPacked
|
|
163
|
+
else if (e.rect.wasPacked && turns?.rects.has(e.rect) === true) {
|
|
164
|
+
// The half gap is the offset less the margin. The margin of the turned item gives the new offset.
|
|
165
|
+
const half = e.dx - marginOf(item).left;
|
|
166
|
+
const m = marginOf(turns.turn(item));
|
|
167
|
+
placements.push({ id: item.id, x: e.rect.position.x + m.left + half + frame.ox, y: e.rect.position.y + m.top + half + frame.oy, turn: 90 });
|
|
168
|
+
} else if (e.rect.wasPacked) placements.push({ id: item.id, x: e.rect.position.x + e.dx + frame.ox, y: e.rect.position.y + e.dy + frame.oy });
|
|
143
169
|
else overflow.push(item.id);
|
|
144
170
|
}
|
|
145
|
-
return { placements, overflow, size: sizeOf(input, frame, placements, moved), moved, trace };
|
|
171
|
+
return { placements, overflow, size: sizeOf(input, frame, placements, moved, turns), moved, trace };
|
|
146
172
|
}
|
|
147
173
|
|
|
148
174
|
/**
|
|
149
175
|
* This function gives the size of the container after a packing. A fixed side keeps its size. A free side goes to the
|
|
150
176
|
* far edge of the items with their margins and the inset, or of the obstacles.
|
|
151
177
|
*/
|
|
152
|
-
function sizeOf(input: PackInput, frame: Frame, placements: Placement[], moved: PackOutput["moved"]): { w: number; h: number } {
|
|
178
|
+
function sizeOf(input: PackInput, frame: Frame, placements: Placement[], moved: PackOutput["moved"], turns?: FrameTurns): { w: number; h: number } {
|
|
153
179
|
const items = new Map(input.items.map((i) => [i.id, i]));
|
|
154
180
|
let right = 0;
|
|
155
181
|
let bottom = 0;
|
|
156
182
|
for (const p of placements) {
|
|
157
|
-
const
|
|
183
|
+
const given = items.get(p.id)!;
|
|
184
|
+
const item = p.turn === 90 && turns !== undefined ? turns.turn(given) : given;
|
|
158
185
|
const m = marginOf(item);
|
|
159
186
|
right = Math.max(right, p.x + item.w + m.right + frame.inset - frame.bounds.x);
|
|
160
187
|
bottom = Math.max(bottom, p.y + item.h + m.bottom + frame.inset - frame.bounds.y);
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,8 @@ export { Rect } from "./rect.ts";
|
|
|
3
3
|
export { pack, packRows, type PackOptions, type PackLayoutOptions } from "./pack.ts";
|
|
4
4
|
export { packSkyline, lowestWindow, type SkylineOptions } from "./skyline.ts";
|
|
5
5
|
export { freeSpace, type FreeSpace, type FreeSpan, type SpaceRect } from "./space.ts";
|
|
6
|
-
export { packShelf, type ShelfOptions } from "./shelf.ts";
|
|
6
|
+
export { packShelf, SHELF_FITS, type ShelfFit, type ShelfOptions } from "./shelf.ts";
|
|
7
|
+
export { packMaxRects, MAXRECTS_BANDS, MAXRECTS_RULES, type MaxRectsOptions, type MaxRectsRule } from "./maxrects.ts";
|
|
7
8
|
export { packFree, aspectError, type FreeWidthOptions, type FreeWidthResult, type FreeWidthTry } from "./free.ts";
|
|
8
9
|
export { compareRects, SORT_ORDERS, type SortOrder } from "./order.ts";
|
|
9
10
|
export { pushCascade, type PushOptions } from "./push.ts";
|
|
@@ -66,7 +67,17 @@ export {
|
|
|
66
67
|
type Strategy,
|
|
67
68
|
type Supports,
|
|
68
69
|
} from "./strategy.ts";
|
|
69
|
-
export {
|
|
70
|
+
export {
|
|
71
|
+
logSweep,
|
|
72
|
+
maxrects,
|
|
73
|
+
push,
|
|
74
|
+
rows,
|
|
75
|
+
shelf,
|
|
76
|
+
skyline,
|
|
77
|
+
type LayoutStrategyOptions,
|
|
78
|
+
type MaxRectsStrategyOptions,
|
|
79
|
+
type PushStrategyOptions,
|
|
80
|
+
} from "./adapters.ts";
|
|
70
81
|
export { fromRects, toRects } from "./convert.ts";
|
|
71
82
|
export { packWith, strategies, strategy } from "./registry.ts";
|
|
72
83
|
export { checkHard, type ContainerTest, type PackHard } from "./check.ts";
|