Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
import { EXIF_CODES, ExifError } from "../../errors/exif-error.js";
import type { ByteOrder, Rational } from "../../types.js";
/**
* Low-level binary reader for TIFF/EXIF byte streams.
*
* Supports both big-endian and little-endian byte orders with runtime switching.
* All read operations are bounds-checked and throw ExifError on overflow.
*/
export class ByteReader {
protected byteOrder: ByteOrder;
protected view: DataView;
protected data: Uint8Array;
constructor(data: Uint8Array, byteOrder: ByteOrder = "BE") {
this.data = data;
this.byteOrder = byteOrder;
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
}
setByteOrder(order: ByteOrder): void {
this.byteOrder = order;
}
get length(): number {
return this.data.byteLength;
}
protected assertBounds(offset: number, size: number): void {
if (offset < 0 || offset + size > this.data.byteLength) {
throw new ExifError(
`Read out of bounds: offset=${offset}, size=${size}, length=${this.data.byteLength}`,
EXIF_CODES.PARSE_ERROR
);
}
}
readUint8(offset: number): number {
this.assertBounds(offset, 1);
return this.view.getUint8(offset);
}
readUint16(offset: number): number {
this.assertBounds(offset, 2);
return this.view.getUint16(offset, this.byteOrder === "LE");
}
readUint32(offset: number): number {
this.assertBounds(offset, 4);
return this.view.getUint32(offset, this.byteOrder === "LE");
}
readInt16(offset: number): number {
this.assertBounds(offset, 2);
return this.view.getInt16(offset, this.byteOrder === "LE");
}
readInt32(offset: number): number {
this.assertBounds(offset, 4);
return this.view.getInt32(offset, this.byteOrder === "LE");
}
readFloat32(offset: number): number {
this.assertBounds(offset, 4);
return this.view.getFloat32(offset, this.byteOrder === "LE");
}
readFloat64(offset: number): number {
this.assertBounds(offset, 8);
return this.view.getFloat64(offset, this.byteOrder === "LE");
}
readBytes(offset: number, length: number): Uint8Array {
this.assertBounds(offset, length);
return this.data.slice(offset, offset + length);
}
readString(offset: number, length: number): string {
this.assertBounds(offset, length);
const bytes = this.data.slice(offset, offset + length);
let str = "";
for (let i = 0; i < bytes.length; i++) {
const b = bytes[i] as number;
if (b === 0) break; // null-terminate on first null
str += String.fromCharCode(b);
}
return str;
}
readNullTermString(offset: number): string {
let end = offset;
while (end < this.data.byteLength && this.data[end] !== 0) {
end++;
}
return this.readString(offset, end - offset);
}
readRational(offset: number): Rational {
this.assertBounds(offset, 8);
const numerator = this.readUint32(offset);
const denominator = this.readUint32(offset + 4);
return { numerator, denominator };
}
readSRational(offset: number): Rational {
this.assertBounds(offset, 8);
const numerator = this.readInt32(offset);
const denominator = this.readInt32(offset + 4);
return { numerator, denominator };
}
slice(start: number, end: number): ByteReader {
const sliced = this.data.slice(start, end);
return new ByteReader(sliced, this.byteOrder);
}
}