package layout import ( "printer.backend/internal/model" ) // Pack computes the maximum grid of items that fit on the plate. // Cells use the full item SVG canvas (product + bleed, margin, crop marks); spacing is between canvases. func Pack(plate model.Plate, spec model.ItemSpec, spacingMM float64) model.LayoutPreview { pw := plate.PrintableWidth() ph := plate.PrintableHeight() footW := spec.ViewportWidth() footH := spec.ViewportHeight() canvasW := spec.CanvasWidthMM() canvasH := spec.CanvasHeightMM() trim := spec.TrimOffsetMM() cw := canvasW + spacingMM ch := canvasH + spacingMM cols, rows := gridCount(pw, ph, cw, ch) count := cols * rows ox := plate.MarginLeft oy := plate.MarginTop positions := make([]model.LayoutPosition, 0, count) for row := 0; row < rows; row++ { for col := 0; col < cols; col++ { positions = append(positions, model.LayoutPosition{ XMM: ox + float64(col)*cw, YMM: oy + float64(row)*ch, }) } } return model.LayoutPreview{ PlateWidthMM: plate.WidthMM, PlateHeightMM: plate.HeightMM, PrintableXMM: ox, PrintableYMM: oy, PrintableWMM: pw, PrintableHMM: ph, CellWidthMM: cw, CellHeightMM: ch, FootprintWMM: footW, FootprintHMM: footH, TrimOffsetMM: trim, CanvasWidthMM: canvasW, CanvasHeightMM: canvasH, Columns: cols, Rows: rows, Count: count, Positions: positions, } } func gridCount(printableW, printableH, cellW, cellH float64) (cols, rows int) { if printableW <= 0 || printableH <= 0 || cellW <= 0 || cellH <= 0 { return 0, 0 } cols = int(printableW / cellW) rows = int(printableH / cellH) if cols < 0 { cols = 0 } if rows < 0 { rows = 0 } return cols, rows }