@mark1russell7/packing 0.2.4 → 0.2.6
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 +27 -2
- package/build/index.d.ts +138 -7
- package/build/index.d.ts.map +1 -1
- package/build/index.js +623 -42
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters.ts +97 -32
- package/src/frame.ts +37 -10
- package/src/index.ts +15 -1
- package/src/layout-input.ts +11 -1
- package/src/maxrects.ts +367 -0
- package/src/moves.ts +15 -0
- package/src/pack.ts +14 -4
- package/src/portfolio.ts +103 -0
- package/src/registry.ts +2 -2
- 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.6",
|
|
5
5
|
"description": "Rectangle packing: shelf, guillotine and skyline strategies, push cascades and measures of a layout.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rectangle-packing",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"node": ">=22.12"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@mark1russell7/layout-contract": "^0.2.
|
|
52
|
+
"@mark1russell7/layout-contract": "^0.2.6"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@mark1russell7/cue": "^1.0.2",
|
package/src/adapters.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
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";
|
|
11
12
|
import type { ShelfFit } from "./shelf.ts";
|
|
@@ -26,6 +27,28 @@ export interface LayoutStrategyOptions {
|
|
|
26
27
|
fit?: ShelfFit;
|
|
27
28
|
}
|
|
28
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
|
+
|
|
29
52
|
/** The options of the strategy `push`. */
|
|
30
53
|
export interface PushStrategyOptions {
|
|
31
54
|
/** The order of the items that the cascade does not place. */
|
|
@@ -61,50 +84,73 @@ function supportsOf(cells: { [D in keyof Supports]: readonly Supports[D][number]
|
|
|
61
84
|
};
|
|
62
85
|
}
|
|
63
86
|
|
|
64
|
-
/**
|
|
65
|
-
function
|
|
66
|
-
|
|
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() };
|
|
67
98
|
}
|
|
68
99
|
|
|
69
|
-
/**
|
|
70
|
-
function
|
|
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
|
+
}
|
|
71
111
|
const out: Record<string, number> = { [MOVES[id]]: moveCount(MOVES[id]) };
|
|
72
|
-
if (
|
|
112
|
+
if (extras.fit !== undefined) for (const move of [MOVES.firstFit, MOVES.bestFit]) out[move] = moveCount(move);
|
|
73
113
|
return out;
|
|
74
114
|
}
|
|
75
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
|
+
|
|
76
121
|
/** This function packs an input with a fixed size or a fixed width with one strategy of `pack`. */
|
|
77
|
-
function packFixedWidth(id:
|
|
122
|
+
function packFixedWidth(id: LayoutId, input: PackInput, options: AnyLayoutOptions, transposed = false): PackOutput {
|
|
78
123
|
const start = now();
|
|
79
|
-
const frame = buildFrame(input);
|
|
124
|
+
const frame = buildFrame(input, false, id === "maxrects" && options.rotate === true);
|
|
80
125
|
const rects = rectsOf(frame);
|
|
81
|
-
const
|
|
126
|
+
const extras = extrasOf(id, options, frame);
|
|
82
127
|
clearMoves();
|
|
83
|
-
if (frame.w > 0) pack(rects, outerOf(frame), { strategy: id, sortOrder: options.sortOrder, space: spaceOf(input, frame), ...
|
|
84
|
-
const output = readFrame(input, frame, []);
|
|
85
|
-
return { ...output, trace: [{ id, ms: now() - start, measures: { "pack.overflow": output.overflow.length, ...movesOf(id,
|
|
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) } }] };
|
|
86
131
|
}
|
|
87
132
|
|
|
88
133
|
/** This function makes the strategy of one function of `pack`. */
|
|
89
|
-
function layoutStrategy(
|
|
90
|
-
id:
|
|
134
|
+
function layoutStrategy<O extends AnyLayoutOptions>(
|
|
135
|
+
id: LayoutId,
|
|
91
136
|
family: string,
|
|
92
137
|
catalog: string[],
|
|
93
|
-
cells: Pick<Supports, "obstacleMove" | "order"
|
|
94
|
-
|
|
95
|
-
|
|
138
|
+
cells: Pick<Supports, "obstacleMove" | "order"> & Partial<Pick<Supports, "rotation">>,
|
|
139
|
+
defaults: O,
|
|
140
|
+
): Strategy<O> {
|
|
141
|
+
const self: Strategy<O> = {
|
|
96
142
|
id,
|
|
97
143
|
family,
|
|
98
144
|
catalog,
|
|
99
145
|
supports: supportsOf({ ...LAYOUT_CELLS, ...cells }),
|
|
100
|
-
defaults: {
|
|
146
|
+
defaults: { ...defaults },
|
|
101
147
|
deterministic: true,
|
|
102
148
|
pack(input, options, context) {
|
|
103
149
|
assertSupported(self.supports, input);
|
|
104
150
|
const o = { ...self.defaults, ...options };
|
|
105
151
|
switch (input.container.fixed) {
|
|
106
152
|
case "height":
|
|
107
|
-
return viaAxes(input, context.previous, (turned) => packFixedWidth(id, turned, o));
|
|
153
|
+
return viaAxes(input, context.previous, (turned) => packFixedWidth(id, turned, o, true));
|
|
108
154
|
case "none":
|
|
109
155
|
return logSweep.search(input, self, context, o);
|
|
110
156
|
default:
|
|
@@ -115,6 +161,9 @@ function layoutStrategy(
|
|
|
115
161
|
return self;
|
|
116
162
|
}
|
|
117
163
|
|
|
164
|
+
/** The defaults of `rows`, `skyline` and `shelf`. */
|
|
165
|
+
const LAYOUT_DEFAULTS: LayoutStrategyOptions = { sortOrder: "height-desc", samples: 48 };
|
|
166
|
+
|
|
118
167
|
/**
|
|
119
168
|
* The strategy `rows` uses `packRows`: a guillotine row packing with a recursive gap fill. With obstacles, locked items or a
|
|
120
169
|
* container shape, the rows go around the holes of the free space and lose the gap fill. Turnable items, groups and item
|
|
@@ -123,7 +172,7 @@ function layoutStrategy(
|
|
|
123
172
|
export const rows: Strategy<LayoutStrategyOptions> = layoutStrategy("rows", "shelf", ["rows-gap-fill", "rows-input"], {
|
|
124
173
|
obstacleMove: ["fixed"],
|
|
125
174
|
order: ["free", "soft", "hard"],
|
|
126
|
-
});
|
|
175
|
+
}, LAYOUT_DEFAULTS);
|
|
127
176
|
|
|
128
177
|
/**
|
|
129
178
|
* The strategy `skyline` uses `packSkyline`: each item goes to the lowest place where it fits. It packs around the
|
|
@@ -132,7 +181,7 @@ export const rows: Strategy<LayoutStrategyOptions> = layoutStrategy("rows", "she
|
|
|
132
181
|
export const skyline: Strategy<LayoutStrategyOptions> = layoutStrategy("skyline", "skyline", ["skyline-bl"], {
|
|
133
182
|
obstacleMove: ["fixed"],
|
|
134
183
|
order: ["free", "soft"],
|
|
135
|
-
});
|
|
184
|
+
}, LAYOUT_DEFAULTS);
|
|
136
185
|
|
|
137
186
|
/**
|
|
138
187
|
* The strategy `shelf` uses `packShelf`: rows filled left to right, around the locked items and the fixed obstacles.
|
|
@@ -142,22 +191,38 @@ export const skyline: Strategy<LayoutStrategyOptions> = layoutStrategy("skyline"
|
|
|
142
191
|
export const shelf: Strategy<LayoutStrategyOptions> = layoutStrategy("shelf", "shelf", ["shelf-nf", "shelf-nfdh"], {
|
|
143
192
|
obstacleMove: ["fixed"],
|
|
144
193
|
order: ["free", "soft", "hard"],
|
|
145
|
-
});
|
|
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
|
+
);
|
|
146
211
|
|
|
147
212
|
/**
|
|
148
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
|
|
149
|
-
* wraps `rows`, `skyline` and `
|
|
214
|
+
* wraps `rows`, `skyline`, `shelf` and `maxrects`. The objective is the area of the frame times `1 + aspectWeight * aspectError`.
|
|
150
215
|
*/
|
|
151
216
|
export const logSweep: SizeSearch = {
|
|
152
217
|
id: "log-sweep",
|
|
153
218
|
search(input: PackInput, inner: Strategy, context: PackContext, options?: unknown): PackOutput {
|
|
154
219
|
if (input.container.fixed !== "none") return inner.pack(input, options ?? inner.defaults, context);
|
|
155
220
|
const id = inner.id;
|
|
156
|
-
if (id !== "rows" && id !== "skyline" && id !== "shelf") throw new UnsupportedCell("containerSize", cellOf(input).containerSize);
|
|
157
|
-
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) };
|
|
158
223
|
const start = now();
|
|
159
|
-
const frame = buildFrame(input);
|
|
160
|
-
const
|
|
224
|
+
const frame = buildFrame(input, false, id === "maxrects" && o.rotate === true);
|
|
225
|
+
const extras = extrasOf(id, o, frame);
|
|
161
226
|
const result = packFree(rectsOf(frame), new Rect<number>(), {
|
|
162
227
|
strategy: id,
|
|
163
228
|
sortOrder: o.sortOrder,
|
|
@@ -165,16 +230,16 @@ export const logSweep: SizeSearch = {
|
|
|
165
230
|
samples: o.samples,
|
|
166
231
|
aspect: input.container.aspect ?? 1,
|
|
167
232
|
aspectWeight: input.container.aspectWeight ?? 1,
|
|
168
|
-
...
|
|
233
|
+
...extras,
|
|
169
234
|
});
|
|
170
235
|
const ms = now() - start;
|
|
171
236
|
const runs = result.tried.length + 1;
|
|
172
|
-
const output = readFrame(input, frame, []);
|
|
237
|
+
const output = readFrame(input, frame, [], [], turnsOf(extras, false));
|
|
173
238
|
return {
|
|
174
239
|
...output,
|
|
175
240
|
trace: [
|
|
176
241
|
{ id: "log-sweep", ms, measures: { "pack.search.widths": result.tried.length, "pack.search.ms": ms } },
|
|
177
|
-
{ id, ms: ms / runs, measures: { "pack.overflow": output.overflow.length, ...movesOf(id,
|
|
242
|
+
{ id, ms: ms / runs, measures: { "pack.overflow": output.overflow.length, ...movesOf(id, extras) } },
|
|
178
243
|
],
|
|
179
244
|
};
|
|
180
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
|
@@ -4,6 +4,7 @@ export { pack, packRows, type PackOptions, type PackLayoutOptions } from "./pack
|
|
|
4
4
|
export { packSkyline, lowestWindow, type SkylineOptions } from "./skyline.ts";
|
|
5
5
|
export { freeSpace, type FreeSpace, type FreeSpan, type SpaceRect } from "./space.ts";
|
|
6
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,15 +67,28 @@ 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";
|
|
83
|
+
export { hardBreaks, packPortfolio, PORTFOLIO_LEVELS, PORTFOLIO_STAGE, type PackPortfolioOptions, type PortfolioEntry } from "./portfolio.ts";
|
|
84
|
+
export type { Portfolio, PortfolioPick, PortfolioTrace, MemberTrace, SelectLevel } from "@mark1russell7/layout-contract";
|
|
72
85
|
export { checkHard, type ContainerTest, type PackHard } from "./check.ts";
|
|
73
86
|
export {
|
|
74
87
|
canonicalJson,
|
|
75
88
|
countCell,
|
|
76
89
|
fnv1a32,
|
|
77
90
|
hashInput,
|
|
91
|
+
outputHash,
|
|
78
92
|
round3,
|
|
79
93
|
toPackInput,
|
|
80
94
|
type CorpusCell,
|
package/src/layout-input.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The input file `layout-input/1` of `docs/FORMAT.md`: its types, its numbers, its canonical form and its hash.
|
|
2
2
|
|
|
3
3
|
import type { LayoutContainer, LayoutEdge, LayoutGenerator, LayoutInput } from "@mark1russell7/layout-contract";
|
|
4
|
-
import type { PackInput } from "./contract.ts";
|
|
4
|
+
import type { PackInput, PackOutput } from "./contract.ts";
|
|
5
5
|
import { sha256Hex, utf8 } from "./sha256.ts";
|
|
6
6
|
import type { Cell } from "./strategy.ts";
|
|
7
7
|
|
|
@@ -90,6 +90,16 @@ export function hashInput(file: LayoutInput): string {
|
|
|
90
90
|
return sha256Hex(canonicalJson(rest)).slice(0, 16);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* This function gives the `outputHash` of an output. It is the first 16 hex characters of the SHA-256 of the canonical form of
|
|
95
|
+
* its placements, sorted by id. The canonical form rounds each number with `round3`. Thus the order of the list does not count.
|
|
96
|
+
* The records of the benchmark and the key of `packPortfolio` use it. It is not `outputHash` of the contract, which uses a grid.
|
|
97
|
+
*/
|
|
98
|
+
export function outputHash(output: Pick<PackOutput, "placements">): string {
|
|
99
|
+
const sorted = output.placements.toSorted((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
100
|
+
return sha256Hex(canonicalJson(sorted)).slice(0, 16);
|
|
101
|
+
}
|
|
102
|
+
|
|
93
103
|
/** This function gives the scale of a count: the nearest of 1, 10, 100, 1000 and 10000 on a log scale. The count 0 gives `1`. */
|
|
94
104
|
export function countCell(count: number): CountScale {
|
|
95
105
|
if (count <= 3) return "1";
|