import {Stack} from 'stack-typescript' import {Point} from '../point' import {Rectangle} from '../rectangle' import {Packing} from './Packing' // Greedily pack rectangles (without rotation) into a given aspect ratio export class GreedyRectanglePacking extends Packing { rectanglesByDescendingHeight: Rectangle[] wrapWidth: number // Constructor for packing, call Run to do the actual pack. // Each RectangleToPack.Rectangle is updated in place. // Pack rectangles tallest to shortest, left to right until wrapWidth is reached, // then wrap to right-most rectangle still with vertical space to fit the next rectangle constructor(rectangles: Rectangle[], wrapWidth: number, rectanglesPresorted = false) { super(null) this.rectsToCenters = new Map() this.rectanglesByDescendingHeight = rectanglesPresorted ? rectangles : GreedyRectanglePacking.SortRectangles(rectangles) this.wrapWidth = wrapWidth } // Sort rectangles by height public static SortRectangles(rectangles: Rectangle[]): Rectangle[] { rectangles.sort((a, b) => b.height - a.height) return rectangles } // Pack rectangles tallest to shortest, left to right until wrapWidth is reached, // then wrap to right-most rectangle still with vertical space to fit the next rectangle run() { this.Pack() } // Traverses the rectangleEnumerator and places rectangles at the next available slot beneath the current parent, // until the parent is filled or until maxRowWidth is reached. Each successfully placed rectangle is pushed onto // a stack, when there is no room for the rectangle we pop the stack for a new parent and try again. Pack() { this.PackedWidth = 0 this.PackedHeight = 0 // get next rectangle const stack = new Stack() let wrap = false let verticalPosition = 0 let packedWidth = 0 let packedHeight = 0 const rects = this.rectanglesByDescendingHeight for (let i = 0; wrap || i < rects.length; ) { const r = rects[i] const parent = stack.length > 0 ? stack.top : null if (parent == null || (parent.right + r.width <= this.wrapWidth && verticalPosition + r.height <= parent.top)) { const leftBottom = new Point(parent ? parent.right : 0, verticalPosition) const center = leftBottom.add(new Point(r.width / 2, r.height / 2)) r.center = center this.rectsToCenters.set(r, center) packedWidth = Math.max(packedWidth, r.right) packedHeight = Math.max(packedHeight, r.top) stack.push(r) wrap = false } else { verticalPosition = parent.top stack.pop() wrap = true } if (!wrap) i++ } this.PackedWidth = packedWidth this.PackedHeight = packedHeight } }