4.1 KiB
ByteReader
(no submodules)
Purpose
Provides a low-level, bounds-checked binary reader for TIFF/EXIF byte streams. It wraps a Uint8Array with a configurable byte order and exposes typed read methods for all TIFF primitive types. The runtime-agnostic implementation avoids Node.js Buffer and browser-specific APIs, relying solely on DataView and standard typed arrays.
Features
- Big-endian and little-endian support with runtime
setByteOrder()switching - Full TIFF primitive coverage: uint8/16/32, int16/32, float32/64, rational, signed rational
- Bounds-checked reads that throw
ExifErrorwithEXIF.PARSE_ERRORon overflow - String reads with null-termination handling (
readString,readNullTermString) slice()factory that creates a sub-reader sharing the same byte order
Usage: Basic Example
import { ByteReader } from "@suaveplan/exif/binary";
const data = new Uint8Array([0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08]);
const reader = new ByteReader(data, "BE");
const magic = reader.readUint16(2); // 0x002A
const ifdOffset = reader.readUint32(4); // 8
Usage: Advanced Example
import { ByteReader } from "@suaveplan/exif/binary";
// Switch byte order mid-stream (e.g. after reading TIFF header)
const reader = new ByteReader(tiffBuffer, "BE");
const bom = reader.readUint16(0);
if (bom === 0x4949) reader.setByteOrder("LE"); // Intel = little-endian
// Read a RATIONAL (8 bytes: numerator + denominator)
const exposureTime = reader.readRational(offset);
console.log(`${exposureTime.numerator}/${exposureTime.denominator}`);
// Sub-slice for IFD parsing
const ifdReader = reader.slice(ifdOffset, ifdOffset + ifdSize);
const entryCount = ifdReader.readUint16(0);
API Reference
constructor(data: Uint8Array, byteOrder?: ByteOrder): ByteReader
Creates a new reader wrapping data. Default byteOrder is "BE" (big-endian).
setByteOrder(order: ByteOrder): void
Switches the active byte order for all subsequent reads.
get length: number
Returns the total byte length of the underlying buffer.
readUint8(offset: number): number
Reads one unsigned byte at offset.
readUint16(offset: number): number
Reads a 2-byte unsigned integer using the active byte order.
readUint32(offset: number): number
Reads a 4-byte unsigned integer using the active byte order.
readInt16(offset: number): number
Reads a 2-byte signed integer using the active byte order.
readInt32(offset: number): number
Reads a 4-byte signed integer using the active byte order.
readFloat32(offset: number): number
Reads a 4-byte IEEE 754 single-precision float using the active byte order.
readFloat64(offset: number): number
Reads an 8-byte IEEE 754 double-precision float using the active byte order.
readBytes(offset: number, length: number): Uint8Array
Returns a copy of length bytes starting at offset.
readString(offset: number, length: number): string
Reads length bytes as a Latin-1 string, stopping at the first null byte.
readNullTermString(offset: number): string
Scans forward from offset until a null byte, then reads the preceding bytes as a string.
readRational(offset: number): Rational
Reads an 8-byte TIFF RATIONAL (two unsigned 32-bit integers: numerator then denominator).
readSRational(offset: number): Rational
Reads an 8-byte TIFF SRATIONAL (two signed 32-bit integers).
slice(start: number, end: number): ByteReader
Returns a new ByteReader over data.slice(start, end) with the same byte order.
Implementation Notes
All read methods call assertBounds before touching DataView to ensure clear error messages on malformed input. The view is constructed from the array's buffer, byteOffset, and byteLength to correctly handle sliced Uint8Array inputs that do not start at offset 0 in their underlying ArrayBuffer. The readString and readNullTermString methods use Latin-1 (one byte per character) which matches TIFF/EXIF ASCII strings; UTF-8 decoding is not attempted here as EXIF ASCII tags are defined as 7-bit ASCII.