# zxing-wasm

[![npm](https://img.shields.io/npm/v/zxing-wasm)](https://www.npmjs.com/package/zxing-wasm/v/latest) [![npm bundle size (scoped)](https://img.shields.io/bundlephobia/minzip/zxing-wasm)](https://www.npmjs.com/package/zxing-wasm/v/latest) [![jsDelivr hits (npm scoped)](https://img.shields.io/jsdelivr/npm/hm/zxing-wasm?color=%23ff5627)](https://cdn.jsdelivr.net/npm/zxing-wasm@latest/) [![Netlify Status](https://api.netlify.com/api/v1/badges/743dfd74-1572-49fb-a758-c1840e174366/deploy-status?branch=main)](https://app.netlify.com/sites/zxing-wasm/deploys)

[ZXing-C++](https://github.com/zxing-cpp/zxing-cpp) WebAssembly as an ES/CJS module with types. Read or write barcodes in your browser (or other JS runtimes like node)!

Visit [this online demo](https://zxing-wasm-demo.netlify.app/) to quickly explore its basic functions. It works best on the latest chromium browsers.

## Build

```bash
git clone --recurse-submodules https://github.com/Sec-ant/zxing-wasm
cd zxing-wasm
npm i
# install cmake first:
# https://cmake.org/download/
npm run cmake
# install emscripten first:
# https://emscripten.org/docs/getting_started/downloads.html
npm run build:wasm
npm run build
```

## Install

```
npm i zxing-wasm
```

## Documentation

https://zxing-wasm.netlify.app/

## Demo

Demo page: https://zxing-wasm-demo.netlify.app/

Demo source: https://github.com/Sec-ant/zxing-wasm-demo

## Usage

This package exports 3 subpaths: `full`, `reader` and `writer`. You can choose whichever fits your needs. If you use TypeScript, you should set [`moduleResolution`](https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution) to [`bundler`](https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler), [`node16` or `nodenext`](https://www.typescriptlang.org/docs/handbook/modules/reference.html#node16-nodenext-1) in your `tsconfig.json` file to properly resolve the exported module.

### `zxing-wasm` or `zxing-wasm/full`

These 2 subpaths include functions to both read and write barcodes. The wasm binary size is ~1.17 MB.

```ts
import {
  readBarcodesFromImageFile,
  readBarcodesFromImageData,
  writeBarcodeToImageFile,
} from "zxing-wasm";
```

or

```ts
import {
  readBarcodesFromImageFile,
  readBarcodesFromImageData,
  writeBarcodeToImageFile,
} from "zxing-wasm/full";
```

### `zxing-wasm/reader`

This subpath only includes functions to read barcodes. The wasm binary size is ~930 KB.

```ts
import {
  readBarcodesFromImageFile,
  readBarcodesFromImageData,
} from "zxing-wasm/reader";
```

### `zxing-wasm/writer`

This subpath only includes a function to write barcodes. The wasm binary size is ~330 KB.

```ts
import { writeBarcodeToImageFile } from "zxing-wasm/writer";
```

### IIFE Scripts

Apart from ES and CJS modules, this package also ships IIFE scripts. The registered global variable is named `ZXingWASM`.

```html
<!-- full -->
<script src="https://cdn.jsdelivr.net/npm/zxing-wasm@<version>/dist/iife/full/index.js"></script>

<!-- reader -->
<script src="https://cdn.jsdelivr.net/npm/zxing-wasm@<version>/dist/iife/reader/index.js"></script>

<!-- writer -->
<script src="https://cdn.jsdelivr.net/npm/zxing-wasm@<version>/dist/iife/writer/index.js"></script>
```

### [`readBarcodesFromImageFile`](https://zxing-wasm.netlify.app/functions/full.readBarcodesFromImageFile.html) and [`readBarcodesFromImageData`](https://zxing-wasm.netlify.app/functions/full.readBarcodesFromImageData.html)

These 2 functions are for reading barcodes.

[`readBarcodesFromImageFile`](https://zxing-wasm.netlify.app/functions/full.readBarcodesFromImageFile.html) accepts an image [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob) or an image [`File`](https://developer.mozilla.org/docs/Web/API/File) as the first input. They're encoded images, e.g. `.png` `.jpg` files.

[`readBarcodesFromImageData`](https://zxing-wasm.netlify.app/functions/full.readBarcodesFromImageData.html) accepts an [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData) as the first input. They're raw pixels that usually acquired from [`<canvas>`](https://developer.mozilla.org/docs/Web/HTML/Element/canvas) or related APIs.

Both of these 2 functions optionally accept the same second input: [`ReaderOptions`](https://zxing-wasm.netlify.app/interfaces/full.ReaderOptions.html).

The return result of these 2 functions is a `Promise` of an array of [`ReadResult`](https://zxing-wasm.netlify.app/interfaces/full.ReadResult.html)s.

e.g.

```ts
import {
  readBarcodesFromImageFile,
  readBarcodesFromImageData,
  type ReaderOptions,
} from "zxing-wasm/reader";

const readerOptions: ReaderOptions = {
  tryHarder: true,
  formats: ["QRCode"],
  maxNumberOfSymbols: 1,
};

/**
 * Read from image file/blob
 */
const imageFile = await fetch(
  "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Hello%20world!",
).then((resp) => resp.blob());

const imageFileReadResults = await readBarcodesFromImageFile(
  imageFile,
  readerOptions,
);

console.log(imageFileReadResults[0].text); // Hello world!

/**
 * Read from image data
 */
const imageData = await createImageBitmap(imageFile).then((imageBitmap) => {
  const { width, height } = imageBitmap;
  const context = new OffscreenCanvas(width, height).getContext(
    "2d",
  ) as OffscreenCanvasRenderingContext2D;
  context.drawImage(imageBitmap, 0, 0, width, height);
  return context.getImageData(0, 0, width, height);
});

const imageDataReadResults = await readBarcodesFromImageData(
  imageData,
  readerOptions,
);

console.log(imageDataReadResults[0].text); // Hello world!
```

### [`writeBarcodeToImageFile`](https://zxing-wasm.netlify.app/functions/full.writeBarcodeToImageFile.html)

This function is used to write barcodes. The first argument of this function is a text string to be encoded and the optional second argument is an [`WriterOptions`](https://zxing-wasm.netlify.app/interfaces/full.WriterOptions.html).

The return result of this function is a `Promise` of a [`WriteResult`](https://zxing-wasm.netlify.app/interfaces/full.WriteResult.html).

e.g.

```ts
import { writeBarcodeToImageFile, type WriterOptions } from "zxing-wasm/writer";

const writerOptions: WriterOptions = {
  format: "QRCode",
  width: 150,
  height: 150,
  margin: 10,
  eccLevel: 2,
};

const writeOutput = await writeBarcodeToImageFile(
  "Hello world!",
  writerOptions,
);

console.log(writeOutput.image);
```

## Notes

When using this package, the `.wasm` binary needs to be served along with the JS glue code. In order to provide a smooth dev experience, the serve path is automatically assigned the [jsDelivr CDN](https://fastly.jsdelivr.net/npm/zxing-wasm/) url upon build.

If you would like to change the serve path (to one of your local network hosts, some other CDNs, or just Base64 encoded data URIs), please use [`setZXingModuleOverrides`](https://zxing-wasm.netlify.app/functions/full.setZXingModuleOverrides.html) to override the [`locateFile`](https://emscripten.org/docs/api_reference/module.html?highlight=locatefile#Module.locateFile) function in advance. `locateFile` is one of the [Emscripten `Module` attribute hooks](https://emscripten.org/docs/api_reference/module.html?highlight=locatefile#affecting-execution) that can affect the code execution of the `Module` object during its lifecycles.

e.g.

```ts
import { setZXingModuleOverrides, writeBarcodeToImageFile } from "zxing-wasm";

// override the locateFile function
setZXingModuleOverrides({
  locateFile: (path, prefix) => {
    if (path.endsWith(".wasm")) {
      return `https://unpkg.com/zxing-wasm@1/dist/full/${path}`;
    }
    return prefix + path;
  },
});

// call read or write functions afterwards
const writeOutput = await writeBarcodeToImageFile("Hello world!");
```

The wasm binary won't be fetched or instantiated unless a [read](#readbarcodesfromimagefile-and-readbarcodesfromimagedata) or [write](#writebarcodetoimagefile) function is firstly called, and will only be instantiated once given the same ([`Object.is`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/is)) [ZXingModuleOverrides](https://zxing-wasm.netlify.app/types/full.ZXingModuleOverrides). If you want to manually trigger the download and instantiation of the wasm binary prior to any read or write functions, you can use [`getZXingModule`](https://zxing-wasm.netlify.app/functions/full.getZXingModule). This function will also return a `Promise` that resolves to a [`ZXingModule`](https://zxing-wasm.netlify.app/types/full.ZXingModule).

```ts
import { getZXingModule } from "zxing-wasm";

/**
 * This function will trigger the download and
 * instantiation of the wasm binary immediately
 */
const zxingModulePromise1 = getZXingModule();

const zxingModulePromise2 = getZXingModule();

console.log(zxingModulePromise1 === zxingModulePromise2); // true
```

[`getZXingModule`](https://zxing-wasm.netlify.app/functions/full.getZXingModule) can also optionally accept a [`ZXingModuleOverrides`](https://zxing-wasm.netlify.app/types/full.ZXingModuleOverrides.html) argument.

```ts
import { getZXingModule } from "zxing-wasm";

getZXingModule({
  locateFile: (path, prefix) => {
    if (path.endsWith(".wasm")) {
      return `https://unpkg.com/zxing-wasm@1/dist/full/${path}`;
    }
    return prefix + path;
  },
});
```

## License

MIT