// Image dimension helpers used to size XLSX floating images by pixels with the // aspect ratio preserved. ExcelJS's two-cell (tl/br) anchoring otherwise stretches // an image to fill a cell range, which distorts logos / signatures / stamps. /** Read pixel dimensions from a PNG / JPEG / WebP buffer (header parse, no deps). */ export function getImageSize(buf: Buffer): { width: number; height: number } | null { // PNG — IHDR width/height at byte offsets 16 / 20 if (buf.length >= 24 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) { return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) }; } // JPEG — scan segments for a Start-Of-Frame marker if (buf.length >= 4 && buf[0] === 0xff && buf[1] === 0xd8) { let o = 2; while (o + 9 < buf.length) { if (buf[o] !== 0xff) { o++; continue; } const m = buf[o + 1]; if (m >= 0xc0 && m <= 0xcf && m !== 0xc4 && m !== 0xc8 && m !== 0xcc) { return { height: buf.readUInt16BE(o + 5), width: buf.readUInt16BE(o + 7) }; } o += 2 + buf.readUInt16BE(o + 2); } } // WebP — RIFF container, VP8 / VP8L / VP8X if (buf.length >= 30 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP") { const fmt = buf.toString("ascii", 12, 16); if (fmt === "VP8 ") return { width: buf.readUInt16LE(26) & 0x3fff, height: buf.readUInt16LE(28) & 0x3fff }; if (fmt === "VP8L") { const b = buf.readUInt32LE(21); return { width: (b & 0x3fff) + 1, height: ((b >> 14) & 0x3fff) + 1 }; } if (fmt === "VP8X") { return { width: 1 + ((buf[24] | (buf[25] << 8) | (buf[26] << 16)) & 0xffffff), height: 1 + ((buf[27] | (buf[28] << 8) | (buf[29] << 16)) & 0xffffff), }; } } return null; } /** Scale natural dimensions to fit within a max box (px), preserving aspect ratio. */ export function scaleToBox( natural: { width: number; height: number }, maxW: number, maxH: number ): { width: number; height: number } { const s = Math.min(maxW / natural.width, maxH / natural.height); return { width: Math.round(natural.width * s), height: Math.round(natural.height * s) }; }