tatry
Version:
Elevation API server.
108 lines (86 loc) • 2.91 kB
JavaScript
import { constants, open } from 'node:fs/promises';
import { fieldTagNames, fieldTypeMap } from './constants.js';
async function read(fh, length, position = 0) {
const buffer = Buffer.allocUnsafe(length);
const { bytesRead } = await fh.read(buffer, 0, length, position);
if (bytesRead !== length) {
throw new RangeError(`Requested ${length}, Read: ${bytesRead}`);
}
return new DataView(buffer.buffer, buffer.byteOffset, bytesRead);
}
export async function parse(filename) {
const fh = await open(filename, constants.O_RDONLY);
const fileHeader = await header(fh);
const entries = await ifd(fileHeader);
const fileDirectory = entries.reduce((o, { name, value }) => {
o[name] = value;
return o;
}, {});
return {
fh,
fileDirectory,
littleEndian: fileHeader.littleEndian
};
}
export async function header(fh) {
const dv = await read(fh, 8);
const littleEndian = dv.getUint16(0) === 0x4949;
const versionNumber = dv.getUint16(2, littleEndian);
if (versionNumber !== 42) {
throw new Error(`Not a TIFF file: ${versionNumber}`);
}
return {
fh,
littleEndian,
ifdOffset: dv.getUint32(4, littleEndian)
};
}
const IFD_ENTRY_SIZE = 12; // word, word, double word, double word
export async function ifd({ fh, littleEndian, ifdOffset }) {
const dv = await read(fh, 2, ifdOffset);
const count = dv.getUint16(0, littleEndian);
const ifdSize = count * IFD_ENTRY_SIZE;
const ifdView = await read(fh, ifdSize, ifdOffset + 2);
const entries = [];
for (let entryOffset = 0; entryOffset < ifdSize; entryOffset += IFD_ENTRY_SIZE) {
const entry = parseEntry(ifdView, entryOffset);
if (!entry) {
continue;
}
if ('value' in entry) {
entries.push(entry);
} else {
entries.push(resolveEntry(entry));
}
}
return Promise.all(entries);
function parseEntry(view, offset) {
const fieldTag = view.getUint16(offset, littleEndian);
const name = fieldTagNames[fieldTag];
if (!name) {
return;
}
const fieldType = view.getUint16(offset + 2, littleEndian);
const { len, getFn } = fieldTypeMap[fieldType];
const size = view.getUint32(offset + 4, littleEndian);
const totalLen = size * len;
return totalLen > 4
? { name, totalLen, len, size, getFn, valueOffset: view.getUint32(offset + 8, littleEndian) }
: { name, value: getFn.call(view, offset + 8, littleEndian) };
}
async function resolveEntry({ name, totalLen, len, size, getFn, valueOffset }) {
const view = await read(fh, totalLen, valueOffset);
if (size === 1) {
return {
name,
value: getFn.call(view, valueOffset, littleEndian)
};
}
const value = [];
for (let offset = 0; offset < totalLen; offset += len) {
const v = getFn.call(view, offset, littleEndian);
value.push(v);
}
return { name, value };
}
}