Write a Polyfill for the Barcode Detection Web API
In the previous article, we’ve talked about how to use the Barcode Detection API to create a web QR code scanner. However, not many browsers support the API. So in this article, we are going to write a polyfill for the Barcode Detection API with the JavaScript version of Dynamsoft Barcode Reader so that the API can work on all major browsers.
This article is Part 2 in a 2-Part Series.
Writing the Barcode Detection API Polyfill
Let’s do this in steps.
Create a New Project and Make Configurations
- Run
npm init
to create a new project. - Install microbundle as the bundler:
npm i -D microbundle
. - Install Dynamsoft Barcode Reader:
npm i dynamsoft-javascript-barcode
. -
In the
package.json
, add the following:{ "type": "module", "source": "src/barcode-detector.ts", "main": "./dist/barcode-detector.js", "exports": { "require": "./dist/barcode-detector.cjs", "default": "./dist/barcode-detector.modern.js" }, "unpkg": "./dist/barcode-detector.umd.js", "amdName": "BarcodeDetectorPolyfill", "scripts": { "build": "microbundle build --globals dynamsoft-javascript-barcode=Dynamsoft.DBR", "dev": "microbundle watch" } }
- In
src
, createbarcode-detector.ts
as the entry file,BarcodeDetectorDBR.ts
which contains the implementation andDefinitions.ts
for defining types and interfaces.
Define Types and Interfaces
In BarcodeDetectorDBR.ts
, define the methods according to the spec:
import { BarcodeDetectorOptions, BarcodeFormat, DetectedBarcode } from "./Definitions";
export default class BarcodeDetector {
constructor(barcodeDetectorOptions?: BarcodeDetectorOptions);
static getSupportedFormats(): Promise<BarcodeFormat[]>;
detect(image: ImageBitmapSource): Promise<DetectedBarcode[]>;
}
In Definitions.ts
, define related interfaces and types.
-
Barcode formats
export type BarcodeFormat = "aztec" | "code_128" | "code_11" | "code_39" | "code_93" | "codabar" | "data_matrix" | "ean_13" | "ean_8" | "itf" | "pdf417" | "qr_code" | "micro_qr_code" | "maxi_code" | "upc_a" | "upc_e" | "gs1_composite" | "gs1_databar" | "gs1_databar_expanded" | "gs1_databar_expanded_stacked" | "gs1_databar_limited" | "gs1_databar_omnidirectional" | "gs1_databar_stacked" | "gs1_databar_stacked_omnidirectional" | "gs1_databar_truncated" | "unknown"
The spec only specifies 13 barcode formats while Dynamsoft Barcode Reader supports more formats, like Code 11, Maxi Code and GS1 Databar, so some extra barcode format names are defined.
-
BarcodeDetectorOptions
export interface BarcodeDetectorOptions { formats? : Array<BarcodeFormat> };
-
Point2D
export interface Point2D { x : Number, y : Number };
-
DetectedBarcode
export interface DetectedBarcode { boundingBox : DOMRectReadOnly, rawValue : String, format : BarcodeFormat, cornerPoints : ReadonlyArray<Point2D> };
In the entry file barcode-detector.ts
, export the above files:
import BarcodeDetector from './BarcodeDetectorDBR';
export * from './BarcodeDetectorDBR';
export * from './Definitions';
export default BarcodeDetector;
Implement the Barcode Detector
Implement getSupportedFormats
Dynamsoft Barcode Reader has barcode formats enumerations. We need to map them with the barcode format names defined in the spec.
const mapFormat = new Map<BarcodeFormat, EnumBarcodeFormat>([
[ "aztec", EnumBarcodeFormat.BF_AZTEC ],
[ "codabar", EnumBarcodeFormat.BF_CODABAR],
[ "code_11", EnumBarcodeFormat.BF_CODE_11 ],
[ "code_39", EnumBarcodeFormat.BF_CODE_39 ],
[ "code_93", EnumBarcodeFormat.BF_CODE_93 ],
[ "code_128", EnumBarcodeFormat.BF_CODE_128 ],
[ "data_matrix", EnumBarcodeFormat.BF_DATAMATRIX],
[ "ean_8", EnumBarcodeFormat.BF_EAN_8 ],
[ "ean_13", EnumBarcodeFormat.BF_EAN_13 ],
[ "itf", EnumBarcodeFormat.BF_ITF ],
[ "pdf417", EnumBarcodeFormat.BF_PDF417 ],
[ "qr_code", EnumBarcodeFormat.BF_QR_CODE ],
[ "micro_qr_code", EnumBarcodeFormat.BF_MICRO_QR ],
[ "gs1_composite", EnumBarcodeFormat.BF_GS1_COMPOSITE],
[ "gs1_databar", EnumBarcodeFormat.BF_GS1_DATABAR],
[ "gs1_databar_expanded", EnumBarcodeFormat.BF_GS1_DATABAR_EXPANDED],
[ "gs1_databar_expanded_stacked", EnumBarcodeFormat.BF_GS1_DATABAR_EXPANDED_STACKED],
[ "gs1_databar_limited", EnumBarcodeFormat.BF_GS1_DATABAR_LIMITED],
[ "gs1_databar_omnidirectional", EnumBarcodeFormat.BF_GS1_DATABAR_OMNIDIRECTIONAL],
[ "gs1_databar_stacked", EnumBarcodeFormat.BF_GS1_DATABAR_STACKED],
[ "gs1_databar_stacked_omnidirectional", EnumBarcodeFormat.BF_GS1_DATABAR_STACKED_OMNIDIRECTIONAL],
[ "gs1_databar_truncated", EnumBarcodeFormat.BF_GS1_DATABAR_TRUNCATED],
[ "maxi_code", EnumBarcodeFormat.BF_MAXICODE ],
[ "upc_a", EnumBarcodeFormat.BF_UPC_A ],
[ "upc_e", EnumBarcodeFormat.BF_UPC_E ]
])
const mapFormatInv = new Map<EnumBarcodeFormat, BarcodeFormat>(
Array.from(mapFormat).map(([key, val]) => [val, key])
)
Then, we can just return the array of BarcodeFormat
in the getSupportedFormats
method.
const allSupportedFormats : BarcodeFormat[] = Array.from(mapFormat.keys())
export default class BarcodeDetector {
static async getSupportedFormats() : Promise<BarcodeFormat[]> {
return allSupportedFormats
}
}
Implement the Contructor
We need to check supported barcode formats and set barcode formats to use according to the spec in the constructor.
private formats: BarcodeFormat[];
constructor (barcodeDetectorOptions? : BarcodeDetectorOptions) {
this.formats = barcodeDetectorOptions?.formats ?? allSupportedFormats
// SPEC: If barcodeDetectorOptions.formats is present and empty, then throw a new TypeError.
if (this.formats.length === 0) {
throw new TypeError("")
}
// SPEC: If barcodeDetectorOptions.formats is present and contains unknown, then throw a new TypeError.
if (this.formats.includes("unknown")) {
throw new TypeError("")
}
}
Then, we need to create a BarcodeReader
instance of Dynamsoft Barcode Reader.
Here, we add a static init
method to do this. If barcode formats are specified, update the BarcodeReader
’s runtime settings.
let reader: BarcodeReader;
static async init() : Promise<BarcodeReader> {
reader = await BarcodeReader.createInstance();
if (this.formats.length != allSupportedFormats.length) {
console.log("update runtime settings for formats");
let settings = await reader.getRuntimeSettings();
let ids:number;
for (let index = 0; index < this.formats.length; index++) {
if (index === 0) {
ids = mapFormat.get(this.formats[index]);
}else{
ids = ids || mapFormat.get(this.formats[index]);
}
}
settings.barcodeFormatIds = ids;
await reader.updateRuntimeSettings(settings);
}
return reader;
}
Because it requires a license for activation, we need to define methods for setting its license. The license has to be set before the creation of an instance.
static setLicense(license:string) {
BarcodeReader.license = license;
}
static getLicense(license:string) : string {
return BarcodeReader.license;
}
Implement detect
The detect
method in the spec has an ImageBitmapSource
argument, while Dynamsoft Barcode Reader can read the following types of data: Blob | Buffer | ArrayBuffer | Uint8Array | Uint8ClampedArray | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | string | DCEFrame | DSImage
. Here we just pass it to the reader’s decode
method as normally, we only need to decode image elements, canvas elements and video elements and Dynamsoft Barcode Reader supports all of them.
async detect(image : ImageBitmapSource) : Promise<DetectedBarcode[]> {
if (!reader) {
throw new Error("Dynamsoft Barcode Reader has not been initialized.");
}
let results:TextResult[] = await reader.decode(image as any);
let detectedBarcodes:DetectedBarcode[] = [];
results.forEach(result => {
let detectedBarcode:DetectedBarcode = this.wrapResult(result);
detectedBarcodes.push(detectedBarcode);
});
return detectedBarcodes;
}
As for the returned barcodes array, we need to wrap the TextResult
of Dynamsoft Barcode Reader to DetectedBarcode
.
wrapResult(result:TextResult):DetectedBarcode{
const cornerPoints = [];
let minX: number, minY: number, maxX: number, maxY: number;
//set initial values
minX = result.localizationResult.x1;
minY = result.localizationResult.y1;
maxX = result.localizationResult.x1;
maxY = result.localizationResult.y1;
for (let index = 1; index < 5; index++) {
const x = result.localizationResult["x"+index];
const y = result.localizationResult["y"+index];
minX = Math.min(x,minX);
minY = Math.min(y,minY);
maxX = Math.max(x,maxX);
maxY = Math.max(y,maxY);
let point:Point2D = {x:x,y:y};
cornerPoints.push(point);
}
let boundingBox = new DOMRectReadOnly(minX, minY, maxX - minX, maxY - minY);
return {
boundingBox: boundingBox,
rawValue: result.barcodeText,
format: mapFormatInv.get(result.barcodeFormat),
cornerPoints
};
}
Build and Use the Library
Build the Library
Now that we’ve finished the library, we can build it with the following command to use:
npm run build
This will output the following files in dist
:
Definitions.d.ts
barcode-detector.cjs
barcode-detector.cjs.map
barcode-detector.d.ts
barcode-detector.esm.js
barcode-detector.esm.js.map
barcode-detector.modern.js
barcode-detector.modern.js.map
barcode-detector.umd.js
barcode-detector.umd.js.map
Use the Library
We can use the library using a <script>
tag:
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.0.2/dist/dbr.js"></script>
<script src="https://cdn.jsdelivr.net/npm/barcode-detection@latest/dist/barcode-detector.umd.js"></script>
Or use it in a project using webpack or other bundlers:
Install it:
npm install barcode-detection // the package has been published to npmjs
Import it:
import {default as BarcodeDetectorPolyfill} from "barcode-detection"
After that, we can use the polyfill if the browser does not support the Barcode Detection API.
if ("BarcodeDetector" in window) {
alert('Barcode Detector supported!');
}else{
alert('Barcode Detector is not supported by this browser, using the Dynamsoft Barcode Reader polyfill.');
//initialize the Dynamsoft Barcode Reader with a license
BarcodeDetectorPolyfill.setLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
await BarcodeDetectorPolyfill.init();
window.BarcodeDetector = BarcodeDetectorPolyfill;
}
barcodeDetector = new window.BarcodeDetector({ formats: ["qr_code"] });
Source Code
You can check out the polyfill’s source code and online demos to learn more: