Files
skills/docs/media/image/exif/src/binary/reader/byte-reader.md
2026-05-29 08:20:56 +01:00

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 ExifError with EXIF.PARSE_ERROR on 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.