Skip to content

Notes on the Raw BWIPP Data

metafloor edited this page Dec 4, 2019 · 3 revisions

Version 2 of bwip-js includes the ability to extract the low-level BWIPP data that is used to generate a barcode image. The following are some notes on this feature.

The examples below were generated using nodejs and the file examples/raw.js found under the bwip-js directory. To understand the output, let's look at the returned data for a code128 barcode:

$ node raw code128 ABCD0123456789
0:
  bbs: [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ]
  bhs: [ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ]
  sbs: [ 2,1,1,2,1,4,1,1,1,3,2,3,1,3,1,1,2,3,1,3,1,3,2, ...snip... ,4,2,1,1,2,3,3,1,1,1,2 ]

PostScript is a stack-based language and data is passed between functions by explicitly pushing the function arguments onto the data stack. The bwip-js raw() method sets a flag inside the BWIPP code to prevent the rendering functions from executing, preserving the data on the stack.

The return value from raw() is an array of objects. The array is a copy of the PostScript data stack. The objects in the array are the PostScript "dictionary" objects that are passed to the BWIPP rendering functions.

The output from raw.js is a friendlier JSON-like format. In the example above, you see a "0:" to indicate the [0] entry of the array followed by the indented properties and values of the dictionary object (returned as a standard JavaScript object).

The property names will change depending on whether you are working with a linear barcode (like code128) or a 2D barcode such as qrcode. Here is what qrcode data looks like:

$ node raw qrcode "This is QRCode"
0:
  pixs: [ 1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,1, ...snip... 1,0,1,1,1,0,0,0,1,1,0,1,0 ]
  pixx: 21
  pixy: 21
  height: 0.5833333333333334
  width: 0.5833333333333334

For linear barcodes, you will have three values per object:

  • sbs : This is the bars and spaces width array. The values are usually integers except when dealing with variable-ratio codes like code39 or 4-state postal codes. For the code128 example above, the values indicate a 2-unit bar, a 1-unit space, a 1-unit bar, a 2-unit space, etc.
  • bhs : The bar heights array. The values are in inches (internally, BWIPP works in units of points and inches). To convert to millimeters, multiply by 25.4. There will be one entry per bar in the corresponding sbs array.
  • bbs : The baseline offset (y offset) for each bar. If you think about UPC, EAN or postal 4-state codes, the bars are positioned at different offsets from the baseline. The values are in inches.

The sbs array length is always odd as it starts and ends with a bar width. The bhs and bbs array lengths will always be (sbs.length + 1) / 2.

Let's look at an example that shows how the bar heights and baseline offsets work. First an EAN-13 that does not have different bar lengths:

$ node raw ean13 2112345678900
0:
  sbs: [ 1,1,1,2,2,2,1,2,2,2,1,2,2,1,2,1,1,4,1,1,1,3,2,1,3,2,1, ...snip... 3,1,1,2,3,2,1,1,3,2,1,1,1,1,1 ]
  bhs: [ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ]
  bbs: [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ]

And now the same barcode with the different bar lengths and offsets:

$ node raw ean13 2112345678900 includetext
0:
  sbs: [ 1,1,1,2,2,2,1,2,2,2,1,2,2,1,2,1,1,4,1,1,1,3,2,1,3,2,1, ...snip... 3,1,1,2,3,2,1,1,3,2,1,1,1,1,1 ]
  bhs: [ 1,1,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,
         1,1,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,1,1 ]
  bbs: [ 0,0,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,
         0,0,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0,0 ]

The text above is formatted so the corresponding bhs and bbs array values line up. Notice how the "long" bars (1 inch) have zero y-offset, whereas the shorter bars (0.925) are given an offset (0.075) so everything ends up top aligned.

For 2D barcodes, the values returned will be:

  • pixs : The "pixel" array. A 1 means the module is black. Most 2d barcodes (the exception is maxicode) are drawn on a grid and this array tells you which grid squares (or rectangles - see below) get colored in.
  • pixx : The width of the grid, in modules.
  • pixy : The height of the grid, in modules.
  • width : How wide the symbol should be drawn, in inches.
  • height : How high the symbol should be drawn, in inches.

The length of the pixs array will always equal to pixx * pixy.

The width and height define the minimum symbol size (per the symbol's spec). That is a little cumbersome and I prefer to use those values to define the shape (ratio between width and height) of each module. For example, take the qrcode example above. The interesting values are:

  pixx: 21
  pixy: 21
  height: 0.5833333333333334
  width: 0.5833333333333334

A module "unit width" is width / pixx, and a module "unit height" is height / pixy. Since qrcode uses a square module, we would expect those values to be the same. pdf417 makes it more interesting:

$ node raw pdf417compact "This is PDF417"
0:
  pixs: [ 1,1,1,1,1,1,1,1,0,1,0,1,0,1,0,0,0,1,1,1,1,1,0,1, ...snip... 1,0,1,0,0,0,1 ]
  pixx: 69
  pixy: 9
  height: 0.375
  width: 0.9583333333333334

Unit-width is 0.95833 / 69 == 0.01389 and unit-height is 0.375 / 9 == 0.04167, resulting in a width:height ratio of 1:3, exactly what you would expect for pdf417.

There are some barcode types that are beyond the scope of these notes.

First is maxicode. It has a separate rendering function in BWIPP, and the encoding of the pixs array is overloaded to provide the x,y coordinates of each hexagon. Search for maxicode in src/bwipjs.js under the bwip-js directory to see an example implementation.

The other difficult symbols have the word "composite" in them and cause multiple rendering objects to be returned. Take ean13composite as an example:

$ node raw ean13composite '2112345678900|(99)1234-abcd' includetext
0:
  sbs: [ 1,1,1,2,2,2,1,2,2,2,1,2,2,1,2,1,1,4,1,1,1,3,2,1,3, ...snip... 1,1,2,3,2,1,1,3,2,1,1,1,1,1 ]
  bhs: [ 1,1,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,
         1,1,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,0.925,1,1 ]
  bbs: [ 0,0,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,
         0,0,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0.075,0,0 ]
1:
  width: 1.3472222222222223
  height: 0.08333333333333333
  pixy: 3
  pixx: 97
  pixs: [ 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ...snip... ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0 ]
2:
  pixs: [ 1,1,0,1,1,0,1,1,1,0,1,1,1,0,0,1,1,0,1,1,1,0, ...snip... ,1,0,0,1,0,0,0,0,1,1,1,0,1,0,0,0,1,0,1 ]
  pixx: 99
  pixy: 3
  height: 0.08333333333333333
  width: 1.375

You can see that the return is a mix of linear and 2D rendering objects. How you position them is lost in the BWIPP code where it does moveto graphics commands before calling the rendering functions. If you need to work with these types of barcodes, you may want to kindly ask Terry (the author of BWIPP) to include the relative dx,dy moves as part of the rendering parameters.