Init
This commit is contained in:
commit
0ad405c26e
78 changed files with 9127 additions and 0 deletions
82
src/lib/model/layout.ts
Normal file
82
src/lib/model/layout.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Tree → world-space rectangles (units are PDF points; 1 CSS px = 1 pt in the
|
||||
// untransformed world layer).
|
||||
|
||||
import { branchesOf, type Dir, type Slot, type Tree } from './tree';
|
||||
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Rect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const GAP = 40;
|
||||
|
||||
export function layout(tree: Tree, sizeOf: (id: string) => Size): Map<string, Rect> {
|
||||
const rects = new Map<string, Rect>();
|
||||
let y = 0;
|
||||
for (const trunkId of tree.trunk) {
|
||||
const b = branchesOf(tree, trunkId);
|
||||
const rowIds = [trunkId, ...b.left, ...b.right];
|
||||
const rowHeight = Math.max(...rowIds.map((id) => sizeOf(id).height));
|
||||
const place = (id: string, x: number) => {
|
||||
const s = sizeOf(id);
|
||||
const r = { x, y: y + (rowHeight - s.height) / 2, width: s.width, height: s.height };
|
||||
rects.set(id, r);
|
||||
return r;
|
||||
};
|
||||
|
||||
const trunk = place(trunkId, -sizeOf(trunkId).width / 2);
|
||||
let left = trunk.x;
|
||||
for (const id of b.left) left = place(id, left - GAP - sizeOf(id).width).x;
|
||||
let right = trunk.x + trunk.width;
|
||||
for (const id of b.right) {
|
||||
const r = place(id, right + GAP);
|
||||
right = r.x + r.width;
|
||||
}
|
||||
y += rowHeight + GAP;
|
||||
}
|
||||
return rects;
|
||||
}
|
||||
|
||||
/** Where a ghost tile for `slot` sits, sized like `size`. */
|
||||
export function slotRect(rects: Map<string, Rect>, slot: Slot, size: Size): Rect | null {
|
||||
const a = rects.get(slot.anchor);
|
||||
if (!a) return null;
|
||||
return rectBeside(a, slot.dir, size);
|
||||
}
|
||||
|
||||
export function rectBeside(a: Rect, dir: Dir, size: Size): Rect {
|
||||
const cx = a.x + a.width / 2;
|
||||
const cy = a.y + a.height / 2;
|
||||
switch (dir) {
|
||||
case 'up':
|
||||
return { x: cx - size.width / 2, y: a.y - GAP - size.height, ...size };
|
||||
case 'down':
|
||||
return { x: cx - size.width / 2, y: a.y + a.height + GAP, ...size };
|
||||
case 'left':
|
||||
return { x: a.x - GAP - size.width, y: cy - size.height / 2, ...size };
|
||||
case 'right':
|
||||
return { x: a.x + a.width + GAP, y: cy - size.height / 2, ...size };
|
||||
}
|
||||
}
|
||||
|
||||
export function bounds(rects: Iterable<Rect>): Rect | null {
|
||||
let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity;
|
||||
for (const r of rects) {
|
||||
x1 = Math.min(x1, r.x);
|
||||
y1 = Math.min(y1, r.y);
|
||||
x2 = Math.max(x2, r.x + r.width);
|
||||
y2 = Math.max(y2, r.y + r.height);
|
||||
}
|
||||
return x1 === Infinity ? null : { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
|
||||
}
|
||||
|
||||
export function intersects(a: Rect, b: Rect): boolean {
|
||||
return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue