import { describe, expect, it } from "vitest"; import { jpegExifOrientation, rotateRgba } from "@/utils/image-to-pdf"; /** สร้าง JPEG หลอง (SOI + marker ที่กำหนด + SOI-trailer) สำหรับทดสอบ parser */ function fakeJpeg(markers: Buffer[]): Buffer { return Buffer.concat([Buffer.from([0xff, 0xd8]), ...markers]); } /** APP1 Exif ที่มีแค่ tag Orientation (274) ตาม byte layout จริง */ function app1Exif(orientation: number): Buffer { const tiff = Buffer.alloc(26); tiff.write("II", 0, "ascii"); tiff.writeUInt16LE(42, 2); tiff.writeUInt32LE(8, 4); tiff.writeUInt16LE(1, 8); tiff.writeUInt16LE(274, 10); tiff.writeUInt16LE(3, 12); tiff.writeUInt32LE(1, 14); tiff.writeUInt16LE(orientation, 18); const payload = Buffer.concat([Buffer.from("Exif\0\0", "latin1"), tiff]); const app1 = Buffer.alloc(4 + payload.length); app1[0] = 0xff; app1[1] = 0xe1; app1.writeUInt16BE(payload.length + 2, 2); payload.copy(app1, 4); return app1; } describe("jpegExifOrientation", () => { it("อ่าน orientation จาก APP1 ได้ (ค่า 1–8)", () => { for (const v of [1, 3, 6, 8]) { expect(jpegExifOrientation(fakeJpeg([app1Exif(v)]))).toBe(v); } }); it("ไม่มี EXIF / ไม่ใช่ JPEG = 1", () => { expect(jpegExifOrientation(fakeJpeg([]))).toBe(1); expect(jpegExifOrientation(Buffer.from([0x89, 0x50]))).toBe(1); // PNG signature }); it("ข้าม marker อื่นก่อนเจอ APP1 (เช่น APP0 JFIF)", () => { const app0 = Buffer.from([0xff, 0xe0, 0x00, 0x04, 0x4a, 0x46]); // len=4 expect(jpegExifOrientation(fakeJpeg([app0, app1Exif(6)]))).toBe(6); }); it("APP1 ที่ไม่ใช่ Exif หรือ value ผิดพิสัย = 1", () => { const notExif = Buffer.from([0xff, 0xe1, 0x00, 0x0a, 0x58, 0x4d, 0x4c, 0, 0, 1, 2, 3]); // "XML..." expect(jpegExifOrientation(fakeJpeg([notExif]))).toBe(1); expect(jpegExifOrientation(fakeJpeg([app1Exif(99)]))).toBe(1); }); it("ทนไฟล์เสี้ยว — APP1 ถูกตัดกลางคันไม่โยน exception (คืน 1)", () => { const full = app1Exif(6); for (const cut of [10, 14, 20, 24]) { const truncated = full.subarray(0, cut); expect(jpegExifOrientation(fakeJpeg([truncated]))).toBe(1); } }); it("ข้าม fill bytes (0xFF ซ้ำ) ก่อน marker ได้", () => { const fills = Buffer.from([0xff, 0xff, 0xff]); expect(jpegExifOrientation(fakeJpeg([fills, app1Exif(6)]))).toBe(6); }); }); /** ตาราง 3×2 RGBA — pixel ระบุตำแหน่งด้วยค่าสี r=x, g=y เพื่อตรวจ mapping */ const W = 3; const H = 2; const rgba = new Uint8Array(W * H * 4); for (let y = 0; y < H; y++) { for (let x = 0; x < W; x++) { const i = (y * W + x) * 4; rgba[i] = x; // r = ตำแหน่ง x เดิม rgba[i + 1] = y; // g = ตำแหน่ง y เดิม rgba[i + 2] = 0; rgba[i + 3] = 255; } } const pixel = (data: Uint8Array, x: number, y: number, width: number) => { const i = (y * width + x) * 4; return [data[i], data[i + 1]]; }; describe("rotateRgba", () => { it("orientation 1 = ไม่แปลง", () => { const out = rotateRgba(rgba, W, H, 1); expect(out.width).toBe(W); expect(pixel(out.data, 2, 1, W)).toEqual([2, 1]); }); it("6 = หมุน 90°CW: สลับกว้าง-สูง, มุมซ้ายบนเดิมไปมุมขวาบน", () => { const out = rotateRgba(rgba, W, H, 6); expect(out.width).toBe(H); expect(out.height).toBe(W); expect(pixel(out.data, H - 1, 0, H)).toEqual([0, 0]); // (0,0) → (h-1, 0) expect(pixel(out.data, 0, W - 1, H)).toEqual([W - 1, H - 1]); // (w-1,h-1) → (0, w-1) }); it("5 = transpose และ 7 = anti-transpose (mirror + หมุน)", () => { const t5 = rotateRgba(rgba, W, H, 5); expect(t5.width).toBe(H); expect(pixel(t5.data, 1, 2, H)).toEqual([2, 1]); // (x,y) → (y,x) const t7 = rotateRgba(rgba, W, H, 7); expect(t7.width).toBe(H); expect(pixel(t7.data, H - 1 - 1, W - 1 - 2, H)).toEqual([2, 1]); // (x,y) → (h-1-y, w-1-x) }); it("2 = flip แนวนอน, 3 = 180°, 4 = flip แนวตั้ง", () => { expect(pixel(rotateRgba(rgba, W, H, 2).data, W - 1, 0, W)).toEqual([0, 0]); expect(pixel(rotateRgba(rgba, W, H, 3).data, W - 1, H - 1, W)).toEqual([0, 0]); expect(pixel(rotateRgba(rgba, W, H, 4).data, 0, H - 1, W)).toEqual([0, 0]); }); });