pelagia-portal/App/tests/unit/image-size.test.ts
Hardik 6677ef4fcf
All checks were successful
PR checks / checks (pull_request) Successful in 34s
PR checks / integration (pull_request) Successful in 26s
fix(po): size XLSX export images by pixels (aspect preserved)
The logo, signature, stamp and cancelled watermark were placed with ExcelJS
two-cell (tl/br) anchors, which stretch each image to fill a cell range —
distorting them and making the watermark text small/squished. The PDF looked
fine because CSS sizes by aspect.

- New lib/image-size.ts: getImageSize (PNG/JPEG/WebP header parse) + scaleToBox.
- Export route now places each image with a oneCell `tl` + pixel `ext`,
  aspect preserved and matched to the PDF sizes (logo ≤96×52, signature ≤165×44,
  stamp ≤80×66, watermark ≤880×720).
- Watermark regenerated as a landscape canvas with the text filling it, so it
  spans the page like the PDF instead of sitting small in the centre.
- Unit test for getImageSize + scaleToBox.

Verified structurally: generated XLSX uses oneCellAnchors with fixed pixel
ext sizes (49×52 / 45×44 / 67×66 / 880×629), not stretched cell ranges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:27:15 +05:30

55 lines
1.9 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { getImageSize, scaleToBox } from "@/lib/image-size";
function fakePng(width: number, height: number): Buffer {
const b = Buffer.alloc(24);
b[0] = 0x89; b[1] = 0x50; b[2] = 0x4e; b[3] = 0x47; // PNG signature start
b.writeUInt32BE(width, 16);
b.writeUInt32BE(height, 20);
return b;
}
function fakeJpeg(width: number, height: number): Buffer {
const b = Buffer.alloc(20);
b[0] = 0xff; b[1] = 0xd8; // SOI
b[2] = 0xff; b[3] = 0xc0; // SOF0 marker
b.writeUInt16BE(0x11, 4); // segment length
b[6] = 8; // precision
b.writeUInt16BE(height, 7);
b.writeUInt16BE(width, 9);
return b;
}
describe("getImageSize", () => {
it("reads PNG dimensions", () => {
expect(getImageSize(fakePng(640, 480))).toEqual({ width: 640, height: 480 });
});
it("reads JPEG dimensions from the SOF marker", () => {
expect(getImageSize(fakeJpeg(1024, 768))).toEqual({ width: 1024, height: 768 });
});
it("returns null for non-image data", () => {
expect(getImageSize(Buffer.from("not an image at all"))).toBeNull();
});
});
describe("scaleToBox", () => {
it("preserves a square aspect ratio (downscale by the binding side)", () => {
const r = scaleToBox({ width: 200, height: 200 }, 96, 52);
expect(r.width).toBe(r.height); // stays square — never stretched
expect(r.height).toBeLessThanOrEqual(52);
});
it("fits a wide image to the width and keeps the ratio", () => {
const r = scaleToBox({ width: 360, height: 96 }, 165, 44);
expect(r.width).toBeLessThanOrEqual(165);
expect(r.height).toBeLessThanOrEqual(44);
expect(r.width / r.height).toBeCloseTo(360 / 96, 1);
});
it("keeps the watermark's landscape ratio", () => {
const r = scaleToBox({ width: 1400, height: 1000 }, 880, 720);
expect(r).toEqual({ width: 880, height: 629 });
});
});