46 lines
2.6 KiB
TypeScript
46 lines
2.6 KiB
TypeScript
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(`<svg width="${cropWidth}" height="${cropHeight}"><rect x="${x}" y="${y}" width="${targetWidth}" height="${targetHeight}" fill="none" stroke="#0088ff" stroke-width="1"/></svg>`);
|
|
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;
|
|
}
|