|
| 1 | +import { rmSync, writeFileSync } from 'node:fs'; |
| 2 | +import { tmpdir } from 'node:os'; |
| 3 | +import { join } from 'node:path'; |
| 4 | +import { deflateSync } from 'node:zlib'; |
| 5 | + |
| 6 | +/** |
| 7 | + * A deterministic tiny-PNG fixture for the `nativeImage` integration tests. |
| 8 | + * |
| 9 | + * The bytes are GENERATED in code (a minimal 8-bit RGBA PNG encoder) rather than |
| 10 | + * embedded, so the asserted width/height are guaranteed to match the file the |
| 11 | + * native decoder reads — proving the scalar `getSize` path returns the EXACT |
| 12 | + * known dimensions, not a coincidence of some opaque blob. |
| 13 | + */ |
| 14 | + |
| 15 | +/** The fixture's known dimensions, asserted exactly by the integration tests. */ |
| 16 | +export const TINY_PNG_WIDTH = 3; |
| 17 | +export const TINY_PNG_HEIGHT = 2; |
| 18 | + |
| 19 | +const CRC_TABLE = (() => { |
| 20 | + const table = new Uint32Array(256); |
| 21 | + for (let n = 0; n < 256; n += 1) { |
| 22 | + let c = n; |
| 23 | + for (let k = 0; k < 8; k += 1) { |
| 24 | + c = (c & 1) === 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; |
| 25 | + } |
| 26 | + table[n] = c >>> 0; |
| 27 | + } |
| 28 | + return table; |
| 29 | +})(); |
| 30 | + |
| 31 | +const crc32 = (buffer: Uint8Array): number => { |
| 32 | + let crc = 0xffffffff; |
| 33 | + for (let i = 0; i < buffer.length; i += 1) { |
| 34 | + const byte = buffer[i] ?? 0; |
| 35 | + crc = (CRC_TABLE[(crc ^ byte) & 0xff] ?? 0) ^ (crc >>> 8); |
| 36 | + } |
| 37 | + return (crc ^ 0xffffffff) >>> 0; |
| 38 | +}; |
| 39 | + |
| 40 | +const chunk = (type: string, data: Uint8Array): Buffer => { |
| 41 | + const length = Buffer.alloc(4); |
| 42 | + length.writeUInt32BE(data.length, 0); |
| 43 | + const body = Buffer.concat([Buffer.from(type, 'ascii'), Buffer.from(data)]); |
| 44 | + const crc = Buffer.alloc(4); |
| 45 | + crc.writeUInt32BE(crc32(body), 0); |
| 46 | + return Buffer.concat([length, body, crc]); |
| 47 | +}; |
| 48 | + |
| 49 | +/** Build the fixture's PNG bytes (a {@link TINY_PNG_WIDTH}×{@link TINY_PNG_HEIGHT} opaque-red image). */ |
| 50 | +export const makeTinyPng = (): Uint8Array => { |
| 51 | + const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); |
| 52 | + const ihdr = Buffer.alloc(13); |
| 53 | + ihdr.writeUInt32BE(TINY_PNG_WIDTH, 0); |
| 54 | + ihdr.writeUInt32BE(TINY_PNG_HEIGHT, 4); |
| 55 | + ihdr[8] = 8; // bit depth |
| 56 | + ihdr[9] = 6; // color type: RGBA |
| 57 | + const stride = 1 + TINY_PNG_WIDTH * 4; |
| 58 | + const raw = Buffer.alloc(TINY_PNG_HEIGHT * stride); |
| 59 | + for (let y = 0; y < TINY_PNG_HEIGHT; y += 1) { |
| 60 | + const row = y * stride; |
| 61 | + raw[row] = 0; // filter: none |
| 62 | + for (let x = 0; x < TINY_PNG_WIDTH; x += 1) { |
| 63 | + const p = row + 1 + x * 4; |
| 64 | + raw[p] = 255; // R |
| 65 | + raw[p + 3] = 255; // A |
| 66 | + } |
| 67 | + } |
| 68 | + return new Uint8Array( |
| 69 | + Buffer.concat([ |
| 70 | + signature, |
| 71 | + chunk('IHDR', ihdr), |
| 72 | + chunk('IDAT', deflateSync(raw)), |
| 73 | + chunk('IEND', Buffer.alloc(0)), |
| 74 | + ]), |
| 75 | + ); |
| 76 | +}; |
| 77 | + |
| 78 | +/** Write the fixture PNG to a unique temp file and return its absolute path. */ |
| 79 | +export const writeTinyPngFile = (): string => { |
| 80 | + const path = join(tmpdir(), `sambar-native-image-${process.pid}-${Date.now()}.png`); |
| 81 | + writeFileSync(path, makeTinyPng()); |
| 82 | + return path; |
| 83 | +}; |
| 84 | + |
| 85 | +/** Delete a fixture file written by {@link writeTinyPngFile} (best-effort). */ |
| 86 | +export const removeTinyPngFile = (path: string): void => { |
| 87 | + rmSync(path, { force: true }); |
| 88 | +}; |
0 commit comments