npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rlouie/structjs

v1.0.0

Published

This package acts as a wrapper to the built-in DataView object while also keeping track of file offsets, allowing for a struct style way of accessing file data.

Downloads

7

Readme

StructJS

Code Coverage Minzipped Size

Struct makes it easy to both read and modify ANY file of ANY type, provided you know how that file is structured.

StructJS provides a C style struct interface making reading and editing binary files simple and more self-documenting. StructJS is built with simplicity in mind, is tiny, and has zero dependencies.

Installation

Install the package:

npm install @binary-files/structjs

Usage

As an example, let's write a program that reads in a bitmap image file, displays information about it, then modifies the image. Using information about the file format we know that the file header should look like this:

Bitmap File Header

| Size | Description | |---------|----------| | 2 bytes | File signature | | 4 bytes | File size in bytes | | 4 bytes | Unused | | 4 bytes | Defines the offset from the start of the file to where the actual bitmap pixel image data is stored |

Lets define our struct based the header structure:

import { Struct } from '@binary-files/structjs';

const bitmapHeaderStruct = new Struct(
  Struct.Uint16('signature'),
  Struct.Uint32('fileSize'),
  Struct.Uint32('unused'),
  Struct.Uint32('bitmapDataStart')
);

Each property is defined with it's own type based on the file structure. Now let's read a bitmap file into an ArrayBuffer and call createObject on our struct to read data out of the ArrayBuffer into our object. We pass in the ArrayBuffer to read from, the byte index in the file to start from, and true for little endian. In this case the byte offset is zero since we want to start from the beginning of the file.

const bitmapFileArrayBuffer = readBitmapImageFile();

const bitmapHeader = bitmapHeaderStruct.createObject(bitmapFileArrayBuffer, 0, true);

console.log(bitmpHeader.fileSize);

And that's it. This code will log the bitmap file size to the console using the data read from the file header.

We can actually continue on with this and define out the rest of the file based on the documented structure.

const infoHeaderStruct = new Struct(
  Struct.Uint32('sizeOfInfoHeader'),
  Struct.Uint32('imageWidth'),
  Struct.Uint32('imageHeight'),
  Struct.Uint16('planes'),
  Struct.Uint16('bitsPerPixel'),
  Struct.Uint32('compression'),
  Struct.Uint32('bitmapDataSize'),
  Struct.Uint32('xPixelsPerMeter'),
  Struct.Uint32('yPixelsPerMeter'),
  Struct.Uint32('numberOfColors'),
  Struct.Uint32('importantColors')
);

const pixelDataStruct = new Struct(
  Struct.Uint8('blue'),
  Struct.Uint8('green'),
  Struct.Uint8('red')
);

const infoHeader = infoHeaderStruct.createObject(bitmapFileArrayBuffer, header.byteLength, true);
const numberOfPixels = infoHeader.imageWidth * infoHeader.imageHeight;

After defining our structs, we generate the info header object as before, but now we want to start after the header, so we give a starting point of the byte length of the header. We also calculate how many pixels total are in the image by multiplying the width and height.

For pixels, unlike the headers, we actually need an array of pixels instead of just one. So we call createArray on our struct to get an array of objects built from our struct, starting at the beginning of the bitmap data, and reading number of pixels times, in little endian format. The result will be an array the size of numberOfPixels, where each element of the array is an object defined by our struct.

const pixels = pixelDataStruct.createArray(bitmapFile, header.bitmapDataStart, numberOfPixels, true);

And believe it or not, that's it! We can now read in entire bitmap image files exactly to spec. We can draw the image into a canvas now if we want to with just a little extra code.

const imageData = new ImageData(infoHeader.imageWidth, infoHeader.imageHeight);

pixels.forEach((pixel, index) => {
  const imageDataIndex = index * 4;
  imageData.data[imageDataIndex] = pixel.red;
  imageData.data[imageDataIndex + 1] = pixel.green;
  imageData.data[imageDataIndex + 2] = pixel.blue;
  imageData.data[imageDataIndex + 3] = 255; // Solid opacity
});

canvasContext.putImageData(imageData, 0, 0);

Great! We read in a bitmap image file and drew it to the canvas. But that's not all, the properties of the generated objects are not read only, you can modify the file by modifying the properties. For instance we can turn every other pixel in the bitmap image file blue by modifying the pixels.

pixels.forEach((pixel, index) => {
  if (index % 2 === 0) {
    pixel.red = 0;
    pixel.green = 0;
    pixel.blue = 255;
  }
});

This doesn't just modify your pixel objects, each pixel is tied directly to your binary data. Changing a property of the pixel object actually changes the data in the array buffer. If you download the original ArrayBuffer every other pixel will be blue. You can try this out here, or view the full code here.

Usage in Node

StructJS can be used in node just as on the front-end, just use require instead of import:

const Struct = require('@binary-files/structjs');

And use the .buffer property of the Buffer object returned by fs.readfile:

fs.readFile(path.join(__dirname, 'cartest.bmp'), (err, data) => {
  const header = headerStruct.createObject(data.buffer, 0, true);
  const infoHeader = infoHeaderStruct.createObject(data.buffer, header.byteLength, true);
  const numberOfPixels = infoHeader.imageWidth * infoHeader.imageHeight;
  const pixels = pixelDataStruct.createArray(data.buffer, header.bitmapDataStart, numberOfPixels, true);

  console.log(`Image Size: ${infoHeader.imageWidth} x ${infoHeader.imageHeight}`);
  console.log(`Bits Per Pixel: ${infoHeader.bitsPerPixel}`);
  console.log(`Image size in bytes (width * height * bytes per pixel): ${infoHeader.bitmapDataSize}`);

  pixels.forEach((pixel, index) => {
    if (index % 2 === 0) {
      pixel.red = 0;
      pixel.green = 0;
      pixel.blue = 255;
    }
  });

  fs.writeFile(path.join(__dirname, 'blue-pixels.bmp'), data, () => console.log('File written'));
});

I've omitted redefining the Structs here, see the front-end section for that, or the full example code here.

How it Works

StructJS is a helper class for JavaScript's built in DataView. DataView uses an ArrayBuffer as its storage backing and allows getting and setting of various types. While this works, each getter and setter requires its own offset and its own boolean for endianess:

new DataView(arrayBuffer).setInt16(0, 256, true);

StructJS uses DataView, but also tracks endianess for the entire struct, lets you name the entries, stores their type and size, and keeps track each of their offsets for you. It then creates getters and setters for each property defined at instantiation. Those getters and setters in turn use the struct endianess, property offset, and property type to call the correct get or set on the DataView.

In the first example when we do bitmapHeader.fileSize this runs get fileSize() on the bitmapHeader object. The getter checks that the type of fileSize is 'Uint32', and calls

this.dataView.getUint32(this.offsetTo.fileSize, this.isLittleEndian)

You can in fact replace bitmapHeader.fileSize with bitmapHeader.dataView.getUint32(bitmapHeader.offsetTo.fileSize, bitmapHeader.isLittleEndian) and the code will continue to function. Writing bitmap.fileSize is just much nicer.

API

Constructor

Struct(propertyInfo1, ...propertyInfoN)

Creates a new Struct.

Parameters

propertyInfo1, propertyInfo2, propertyInfoN

One or more property definitions for your struct.

Returns

A Struct instance based on the definitions provided.


Type Definitions

Struct.Int8(propertyName)

Defines 8-bit signed integer.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Int8', byteLength: 1 } used by the Struct constructor.


Struct.Uint8(propertyName)

Defines 8-bit unsigned integer.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Uint8', byteLength: 1 } used by the Struct constructor.


Struct.Int16(propertyName)

Defines 16-bit signed integer.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Int16', byteLength: 2 } used by the Struct constructor.


Struct.Uint16(propertyName)

Defines 16-bit unsigned integer.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Uint16', byteLength: 2 } used by the Struct constructor.


Struct.Int32(propertyName)

Defines 32-bit signed integer.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Int32', byteLength: 4 } used by the Struct constructor.


Struct.Uint32(propertyName)

Defines 32-bit unsigned integer.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Uint32', byteLength: 4 } used by the Struct constructor.


Struct.BigInt64(propertyName)

Defines 64-bit signed big int.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'BigInt64', byteLength: 8 } used by the Struct constructor.


Struct.BigUint64(propertyName)

Defines 64-bit unsigned big int.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'BigUint64', byteLength: 8 } used by the Struct constructor.


Struct.Float32(propertyName)

Defines 32-bit floating point number.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Float32', byteLength: 4 } used by the Struct constructor.


Struct.Float64(propertyName)

Defines 64-bit floating point number.

Parameters

propertyName What you want the property to be named in the resulting object.

Returns

A property info object with the format { propertyName, propertyType: 'Float64', byteLength: 8 } used by the Struct constructor.


Instance Methods

createObject(arrayBuffer, startOffset, isLittleEndian)

Creates an object from an ArrayBuffer as defined by your struct.

Parameters

arrayBuffer ArrayBuffer to create the object from.

startOffset Position in the ArrayBuffer to start from.

isLittleEndian Pass true to use little endian format. Defaults to big endian.

Returns

Object with properties defined in struct, as well as the backing Dataview, a collection of offsets to those properties in the DataView, and the total byte length of the object.


createArray(arrayBuffer, startOffset, numberOfObjects, isLittleEndian)

Creates an array of objects from an ArrayBuffer as defined by your struct.

Parameters

arrayBuffer ArrayBuffer to create the object from.

startOffset Position in the ArrayBuffer to start from.

isLittleEndian Pass true to use little endian format. Defaults to big endian.

Returns

Array of objects with properties defined in struct, as well as the backing Dataview, a collection of offsets to those properties in the DataView, and the total byte length of the object.