All files / src/lib/helpers pngSize.ts

93.1% Statements 27/29
80% Branches 8/10
100% Functions 2/2
92.85% Lines 26/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 732x             2x 2x       2x       4x 4x 4x   4x 4x 5x             5x 1x     4x     3x   4x       2x 4x   3x 1x     2x   2x 1x     1x           1x       1x           2x  
import { open } from "fs/promises";
 
export type PNGSize = {
  width: number;
  height: number;
};
 
const CGBI_HEADER_SIZE = 40;
const PNG_SIGNATURE = Buffer.from([
  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
]);
 
const readHeader = async (
  filename: string,
  length: number,
): Promise<Buffer> => {
  const file = await open(filename, "r");
  try {
    const buffer = Buffer.alloc(length);
 
    let offset = 0;
    while (offset < length) {
      const { bytesRead } = await file.read(
        buffer,
        offset,
        length - offset,
        offset,
      );
 
      if (bytesRead === 0) {
        throw new TypeError("Invalid PNG");
      }
 
      offset += bytesRead;
    }
 
    return buffer;
  } finally {
    await file.close();
  }
};
 
export const pngSize = async (filename: string): Promise<PNGSize> => {
  const header = await readHeader(filename, CGBI_HEADER_SIZE);
 
  if (!header.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {
    throw new TypeError("Invalid PNG");
  }
 
  const firstChunk = header.toString("ascii", 12, 16);
 
  if (firstChunk === "CgBI") {
    Iif (header.toString("ascii", 28, 32) !== "IHDR") {
      throw new TypeError("Invalid PNG");
    }
    return {
      width: header.readUInt32BE(32),
      height: header.readUInt32BE(36),
    };
  }
 
  Iif (firstChunk !== "IHDR") {
    throw new TypeError("Invalid PNG");
  }
 
  return {
    width: header.readUInt32BE(16),
    height: header.readUInt32BE(20),
  };
};
 
export default pngSize;