import sharp from "sharp"; import type { NormalizedReplacementBox } from "@/lib/ai/replacement-layout-lock"; export async function makeLayoutReplaceTextBoards(template: Buffer, result: Buffer, boxes: NormalizedReplacementBox[]) { const metadata = await sharp(template).metadata(); const width = metadata.width!; const height = metadata.height!; const generated = await sharp(result).resize(width, height, { fit: "fill" }).png().toBuffer(); const boards: Buffer[] = []; let overlays: { input: Buffer; left: number; top: number }[] = []; let boardHeight = 10; async function flush() { if (!overlays.length) return; boards.push(await sharp({ create: { width: 1390, height: boardHeight, channels: 3, background: "#d9d9d9" } }).composite(overlays).png().toBuffer()); overlays = []; boardHeight = 10; } for (const box of boxes) { const left = Math.max(0, Math.floor(box[0] / 1000 * width) - 24); const top = Math.max(0, Math.floor(box[1] / 1000 * height) - 24); const right = Math.min(width, Math.ceil(box[2] / 1000 * width) + 24); const bottom = Math.min(height, Math.ceil(box[3] / 1000 * height) + 24); // A tall coupon panel must remain tall. A fixed 150px cell made its small // print unreadable, causing valid text to be reported as missing/changed. const rowHeight = Math.min(1100, Math.max(150, Math.ceil((bottom - top) / (right - left) * 680))); if (boardHeight + rowHeight + 10 > 1320) await flush(); for (const [column, buffer] of [template, generated].entries()) { const cropWidth = right - left; const cropHeight = bottom - top; let cropped = await sharp(buffer).extract({ left, top, width: cropWidth, height: cropHeight }).png().toBuffer(); if (column === 0) { const x = Math.max(1, box[0] / 1000 * width - left); const y = Math.max(1, box[1] / 1000 * height - top); const targetWidth = Math.max(1, Math.min(cropWidth - x - 1, (box[2] - box[0]) / 1000 * width)); const targetHeight = Math.max(1, Math.min(cropHeight - y - 1, (box[3] - box[1]) / 1000 * height)); const outline = Buffer.from(``); cropped = await sharp(cropped).composite([{ input: outline }]).png().toBuffer(); } const input = await sharp(cropped) .resize(680, rowHeight, { fit: "contain", background: "#ffffff" }).png().toBuffer(); overlays.push({ input, left: 10 + column * 690, top: boardHeight }); } boardHeight += rowHeight + 10; } await flush(); return boards; }