Initial Commit
This commit is contained in:
116
packages/media/image/exif/src/binary/reader/byte-reader.ts
Normal file
116
packages/media/image/exif/src/binary/reader/byte-reader.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user