@bwip-js/browser
Version:
JavaScript barcode generator supporting over 100 types and standards.
1,366 lines (1,256 loc) • 188 kB
JavaScript
// This file is part of the bwip-js project available at:
//
// http://metafloor.github.io/bwip-js
//
// Copyright (c) 2011-2025 Mark Warren
//
// This file contains code automatically generated from:
// Barcode Writer in Pure PostScript - Version 2024-06-18
// Copyright (c) 2004-2024 Terry Burton
//
// The MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
"use strict";
import { bwipp_auspost,bwipp_azteccode,bwipp_azteccodecompact,bwipp_aztecrune,bwipp_bc412,bwipp_channelcode,bwipp_codablockf,bwipp_code11,bwipp_code128,bwipp_code16k,bwipp_code2of5,bwipp_code32,bwipp_code39,bwipp_code39ext,bwipp_code49,bwipp_code93,bwipp_code93ext,bwipp_codeone,bwipp_coop2of5,bwipp_daft,bwipp_databarexpanded,bwipp_databarexpandedcomposite,bwipp_databarexpandedstacked,bwipp_databarexpandedstackedcomposite,bwipp_databarlimited,bwipp_databarlimitedcomposite,bwipp_databaromni,bwipp_databaromnicomposite,bwipp_databarstacked,bwipp_databarstackedcomposite,bwipp_databarstackedomni,bwipp_databarstackedomnicomposite,bwipp_databartruncated,bwipp_databartruncatedcomposite,bwipp_datalogic2of5,bwipp_datamatrix,bwipp_datamatrixrectangular,bwipp_datamatrixrectangularextension,bwipp_dotcode,bwipp_ean13,bwipp_ean13composite,bwipp_ean14,bwipp_ean2,bwipp_ean5,bwipp_ean8,bwipp_ean8composite,bwipp_flattermarken,bwipp_gs1_128,bwipp_gs1_128composite,bwipp_gs1_cc,bwipp_gs1datamatrix,bwipp_gs1datamatrixrectangular,bwipp_gs1dldatamatrix,bwipp_gs1dlqrcode,bwipp_gs1dotcode,bwipp_gs1northamericancoupon,bwipp_gs1qrcode,bwipp_hanxin,bwipp_hibcazteccode,bwipp_hibccodablockf,bwipp_hibccode128,bwipp_hibccode39,bwipp_hibcdatamatrix,bwipp_hibcdatamatrixrectangular,bwipp_hibcmicropdf417,bwipp_hibcpdf417,bwipp_hibcqrcode,bwipp_iata2of5,bwipp_identcode,bwipp_industrial2of5,bwipp_interleaved2of5,bwipp_isbn,bwipp_ismn,bwipp_issn,bwipp_itf14,bwipp_jabcode,bwipp_japanpost,bwipp_kix,bwipp_leitcode,bwipp_mailmark,bwipp_mands,bwipp_matrix2of5,bwipp_maxicode,bwipp_micropdf417,bwipp_microqrcode,bwipp_msi,bwipp_onecode,bwipp_pdf417,bwipp_pdf417compact,bwipp_pharmacode,bwipp_pharmacode2,bwipp_planet,bwipp_plessey,bwipp_posicode,bwipp_postnet,bwipp_pzn,bwipp_qrcode,bwipp_rationalizedCodabar,bwipp_raw,bwipp_rectangularmicroqrcode,bwipp_royalmail,bwipp_sscc18,bwipp_swissqrcode,bwipp_symbol,bwipp_telepen,bwipp_telepennumeric,bwipp_ultracode,bwipp_upca,bwipp_upcacomposite,bwipp_upce,bwipp_upcecomposite,bwipp_lookup,bwipp_encode,BWIPP_VERSION } from './bwipp.mjs';
// exports.js
const BWIPJS_VERSION = '4.5.3 (2025-03-20)';
// bwipjs.toCanvas(canvas, options)
// bwipjs.toCanvas(options, canvas)
//
// Uses the built-in canvas drawing.
//
// `canvas` can be an HTMLCanvasElement or an ID string or unique selector string.
// `options` are a bwip-js/BWIPP options object.
//
// This function is synchronous and throws on error.
//
// Returns the HTMLCanvasElement.
function ToCanvas(cvs, opts) {
if (typeof opts == 'string' || opts instanceof HTMLCanvasElement) {
let tmp = cvs;
cvs = opts;
opts = tmp;
}
return _ToAny(bwipp_lookup(opts.bcid), opts, cvs);
}
// Entry point for the symbol-specific exports
//
// Polymorphic internal interface
// _ToAny(encoder, string, opts) : HTMLCanvasElement
// _ToAny(encoder, HTMLCanvasElement, opts) : HTMLCanvasElement
// _ToAny(encoder, opts, string) : HTMLCanvasElement
// _ToAny(encoder, opts, HTMLCanvasElement) : HTMLCanvasElement
// _ToAny(encoder, opts, drawing) : any
//
// 'string` can be either an `id` or query selector returning a single canvas element.
function _ToAny(encoder, opts, drawing) {
if (typeof opts == 'string') {
var canvas = document.getElementById(opts) || document.querySelector(opts);
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('bwipjs: `' + opts + '`: not a canvas');
}
opts = drawing;
drawing = DrawingCanvas(canvas);
} else if (opts instanceof HTMLCanvasElement) {
var canvas = opts;
opts = drawing;
drawing = DrawingCanvas(canvas);
} else if (typeof drawing == 'string') {
var canvas = document.getElementById(drawing) || document.querySelector(drawing);
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('bwipjs: `' + drawing + '`: not a canvas');
}
drawing = DrawingCanvas(canvas);
} else if (drawing instanceof HTMLCanvasElement) {
drawing = DrawingCanvas(drawing);
} else if (!drawing || typeof drawing != 'object' || !drawing.init) {
throw new Error('bwipjs: not a canvas or drawing object');
}
return _Render(encoder, opts, drawing);
}
// bwipjs.toSVG(options)
//
// Uses the built-in svg drawing interface.
//
// `options` are a bwip-js/BWIPP options object.
//
// This function is synchronous and throws on error.
//
// Returns a string containing a fully qualified SVG definition,
// including the natural width and height of the image, in pixels:
//
// <svg viewBox="0 0 242 200" xmlns="http://www.w3.org/2000/svg">
// ...
// </svg>
//
// Available on all platforms.
function ToSVG(opts) {
return _Render(bwipp_lookup(opts.bcid), opts, DrawingSVG());
}
function FixupOptions(opts) {
var scale = opts.scale || 2;
var scaleX = +opts.scaleX || scale;
var scaleY = +opts.scaleY || scaleX;
// Fix up padding.
opts.paddingleft = padding(opts.paddingleft, opts.paddingwidth, opts.padding, scaleX);
opts.paddingright = padding(opts.paddingright, opts.paddingwidth, opts.padding, scaleX);
opts.paddingtop = padding(opts.paddingtop, opts.paddingheight, opts.padding, scaleY);
opts.paddingbottom = padding(opts.paddingbottom, opts.paddingheight, opts.padding, scaleY);
// We override BWIPP's background color functionality. If in CMYK, convert to RRGGBB so
// the drawing interface is consistent. Likewise, if in CSS-style #rgb or #rrggbb.
if (opts.backgroundcolor) {
var bgc = ''+opts.backgroundcolor;
if (/^[0-9a-fA-F]{8}$/.test(bgc)) {
var c = parseInt(bgc.substr(0,2), 16) / 255;
var m = parseInt(bgc.substr(2,2), 16) / 255;
var y = parseInt(bgc.substr(4,2), 16) / 255;
var k = parseInt(bgc.substr(6,2), 16) / 255;
var r = Math.floor((1-c) * (1-k) * 255).toString(16);
var g = Math.floor((1-m) * (1-k) * 255).toString(16);
var b = Math.floor((1-y) * (1-k) * 255).toString(16);
opts.backgroundcolor = (r.length == 1 ? '0' : '') + r +
(g.length == 1 ? '0' : '') + g +
(b.length == 1 ? '0' : '') + b;
} else {
if (bgc[0] == '#') {
bgc = bgc.substr(1);
}
if (/^[0-9a-fA-F]{6}$/.test(bgc)) {
opts.backgroundcolor = bgc;
} else if (/^[0-9a-fA-F]{3}$/.test(bgc)) {
opts.backgroundcolor = bgc[0] + bgc[0] + bgc[1] + bgc[1] + bgc[2] + bgc[2];
} else {
throw new Error('bwip-js: invalid backgroundcolor: ' + opts.backgroundcolor);
}
}
}
return opts;
// a is the most specific padding value, e.g. paddingleft
// b is the next most specific value, e.g. paddingwidth
// c is the general padding value.
// s is the scale, either scalex or scaley
function padding(a, b, c, s) {
if (a != null) {
a = a >>> 0;
return a*s >>> 0;
}
if (b != null) {
b = b >>> 0;
return b*s >>> 0;
}
c = c >>> 0;
return (c*s >>> 0) || 0;
}
}
var BWIPJS_OPTIONS = {
bcid:1,
text:1,
scale:1,
scaleX:1,
scaleY:1,
rotate:1,
padding:1,
paddingwidth:1,
paddingheight:1,
paddingtop:1,
paddingleft:1,
paddingright:1,
paddingbottom:1,
backgroundcolor:1,
};
// bwipjs.render(options, drawing)
//
// Renders a barcode using the provided drawing object.
//
// This function is synchronous and throws on error.
//
// Browser and nodejs usage.
function Render(options, drawing) {
return _Render(bwipp_lookup(options.bcid), options, drawing);
}
// Called by the public exports
function _Render(encoder, options, drawing) {
var text = options.text;
if (!text) {
throw new ReferenceError('bwip-js: bar code text not specified.');
}
// setopts() is optional on the drawing object.
FixupOptions(options);
drawing.setopts && drawing.setopts(options);
// Set the bwip-js defaults
var scale = options.scale || 2;
var scaleX = +options.scaleX || scale;
var scaleY = +options.scaleY || scaleX;
var rotate = options.rotate || 'N';
// Create a barcode writer object. This is the interface between
// the low-level BWIPP code, the bwip-js graphics context, and the
// drawing interface.
var bw = new BWIPJS(drawing);
// Set the BWIPP options
var bwippopts = {};
for (var id in options) {
if (!BWIPJS_OPTIONS[id]) {
bwippopts[id] = options[id];
}
}
// Fix a disconnect in the BWIPP rendering logic
if (bwippopts.alttext) {
bwippopts.includetext = true;
}
// We use mm rather than inches for height - except pharmacode2 height
// which is already in mm.
if (+bwippopts.height && encoder != bwipp_pharmacode2) {
bwippopts.height = bwippopts.height / 25.4 || 0.5;
}
// Likewise, width
if (+bwippopts.width) {
bwippopts.width = bwippopts.width / 25.4 || 0;
}
// Scale the image
bw.scale(scaleX, scaleY);
// Call into the BWIPP cross-compiled code and render the image.
bwipp_encode(bw, encoder, text, bwippopts);
// Returns whatever drawing.end() returns, or `false` if nothing rendered.
return bw.render();
}
// bwipjs.raw(options)
// bwipjs.raw(bcid, text, opts-string)
//
// Invokes the low level BWIPP code and returns the raw encoding data.
//
// This function is synchronous and throws on error.
//
// Browser and nodejs usage.
function ToRaw(bcid, text, options) {
if (arguments.length == 1) {
options = bcid;
bcid = options.bcid;
text = options.text;
}
// The drawing interface is just needed for the pre-init() calls.
// Don't need to fixup the drawing specific options.
var drawing = DrawingBuiltin();
drawing.setopts(options);
var bw = new BWIPJS(drawing);
var stack = bwipp_encode(bw, bwipp_lookup(bcid), text, options, true);
// bwip-js uses Maps to emulate PostScript dictionary objects; but Maps
// are not a typical/expected return value. Convert to plain-old-objects.
var ids = { pixs:1, pixx:1, pixy:1, sbs:1, bbs:1, bhs:1, width:1, height:1 };
for (var i = 0; i < stack.length; i++) {
var elt = stack[i];
if (elt instanceof Map) {
var obj = {};
// Could they make Maps any harder to iterate over???
for (var keys = elt.keys(), size = elt.size, k = 0; k < size; k++) {
var id = keys.next().value;
if (ids[id]) {
var val = elt.get(id);
if (val instanceof Array) {
// The postscript arrays have extra named properties
// to emulate array views. Return cleaned up arrays.
obj[id] = val.b.slice(val.o, val.o + val.length);
} else {
obj[id] = val;
}
}
}
stack[i] = obj;
} else {
// This should never exec...
stack.splice(i--, 1);
}
}
return stack;
}
// file : bwipjs.js
//
// Graphics-context interface to the BWIPP cross-compiled code
var BWIPJS = (function() {
// Math.floor(), etc. are notoriously slow. Caching seems to help.
var floor = Math.floor;
var round = Math.round;
var ceil = Math.ceil;
var min = Math.min;
var max = Math.max;
function BWIPJS(drawing) {
if (this.constructor !== BWIPJS) {
return new BWIPJS(drawing);
}
this.gstk = []; // Graphics save/restore stack
this.cmds = []; // Graphics primitives to replay when rendering
this.drawing = drawing; // Drawing interface
this.reset();
// Drawing surface bounding box
this.minx = this.miny = Infinity;
this.maxx = this.maxy = -Infinity;
};
// All graphics state that must be saved/restored is given a prefix of g_
BWIPJS.prototype.reset = function() {
// Current Transform Matrix - since we don't do rotation, we can fake
// the matrix math
this.g_tdx = 0; // CTM x-offset
this.g_tdy = 0; // CTM y-offset
this.g_tsx = 1; // CTM x-scale factor
this.g_tsy = 1; // CTM y-scale factor
this.g_posx = 0; // current x position
this.g_posy = 0; // current y position
this.g_penw = 1; // current line/pen width
this.g_path = []; // current path
this.g_font = null; // current font object
this.g_rgb = [0,0,0]; // current color (black)
this.g_clip = false; // clip region active
};
BWIPJS.prototype.save = function() {
// clone all g_ properties
var ctx = {};
for (var id in this) {
if (id.indexOf('g_') == 0) {
ctx[id] = clone(this[id]);
}
}
this.gstk.push(ctx);
// Perform a deep clone of the graphics state properties
function clone(v) {
if (v instanceof Array) {
var t = [];
for (var i = 0; i < v.length; i++)
t[i] = clone(v[i]);
return t;
}
if (v instanceof Object) {
var t = {};
for (var id in v)
t[id] = clone(v[id]);
return t;
}
return v;
}
};
BWIPJS.prototype.restore = function() {
if (!this.gstk.length) {
throw new Error('grestore: stack underflow');
}
var ctx = this.gstk.pop();
var self = this;
if (this.g_clip && !ctx.g_clip) {
this.cmds.push(function() {
self.drawing.unclip();
});
}
for (var id in ctx) {
this[id] = ctx[id];
}
};
// Per the postscript spec:
// As discussed in Section 4.4.1, Current Path, points entered into a path
// are immediately converted to device coordinates by the current
// transformation matrix (CTM); subsequent modifications to the CTM do not
// affect existing points. `currentpoint` computes the user space
// coordinates corresponding to the current point according to the current
// value of the CTM. Thus, if a current point is set and then the CTM is
// changed, the coordinates returned by currentpoint will be different
// from those that were originally specified for the point.
BWIPJS.prototype.currpos = function() {
return { x:(this.g_posx-this.g_tdx)/this.g_tsx,
y:(this.g_posy-this.g_tdy)/this.g_tsy
};
};
BWIPJS.prototype.currfont = function() {
return this.g_font;
};
BWIPJS.prototype.translate = function(x, y) {
this.g_tdx = this.g_tsx * x;
this.g_tdy = this.g_tsy * y;
};
BWIPJS.prototype.scale = function(x, y) {
this.g_tsx *= x;
this.g_tsy *= y;
var sxy = this.drawing.scale(this.g_tsx, this.g_tsy);
if (sxy && sxy[0] && sxy[1]) {
this.g_tsx = sxy[0];
this.g_tsy = sxy[1];
}
};
BWIPJS.prototype.setlinewidth = function(w) {
this.g_penw = w;
};
BWIPJS.prototype.selectfont = function(f, z) {
this.g_font = { FontName:this.jsstring(f), FontSize:+z };
};
BWIPJS.prototype.getfont = function() {
return this.g_font.FontName;
};
// Special function for converting a Uint8Array string to string.
BWIPJS.prototype.jsstring = function(s) {
if (s instanceof Uint8Array) {
// Postscript (like C) treats nul-char as end of string.
//for (var i = 0, l = s.length; i < l && s[i]; i++);
//if (i < l) {
// return String.fromCharCode.apply(null,s.subarray(0, i));
//}
return String.fromCharCode.apply(null,s)
}
return ''+s;
};
// Special function to replace setanycolor in BWIPP.
// Converts a string of hex digits either rgb, rrggbb or ccmmyykk.
// Or CSS-style #rgb and #rrggbb.
BWIPJS.prototype.setcolor = function(s) {
if (s instanceof Uint8Array) {
s = this.jsstring(s);
}
if (!s) {
return;
}
if (!/^(?:#?[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?|[0-9a-fA-F]{8})$/.test(s)) {
throw new Error('bwip-js: invalid color: ' + s);
}
if (s[0] == '#') {
s = s.substr(1);
}
if (s.length == 3) {
var r = parseInt(s[0], 16);
var g = parseInt(s[1], 16);
var b = parseInt(s[2], 16);
this.g_rgb = [ r<<4|r, g<<4|g, b<<4|b ];
} else if (s.length == 6) {
var r = parseInt(s.substr(0,2), 16);
var g = parseInt(s.substr(2,2), 16);
var b = parseInt(s.substr(4,2), 16);
this.g_rgb = [ r, g, b ];
} else {
var c = parseInt(s.substr(0,2), 16) / 255;
var m = parseInt(s.substr(2,2), 16) / 255;
var y = parseInt(s.substr(4,2), 16) / 255;
var k = parseInt(s.substr(6,2), 16) / 255;
var r = round((1-c) * (1-k) * 255);
var g = round((1-m) * (1-k) * 255);
var b = round((1-y) * (1-k) * 255);
this.g_rgb = [ r, g, b ];
}
};
// Used only by swissqrcode
BWIPJS.prototype.setrgbcolor = function(r,g,b) {
this.g_rgb = [ r, g, b ];
};
// Returns the current rgb values as a 'RRGGBB'
BWIPJS.prototype.getRGB = function() {
var r = this.g_rgb[0].toString(16);
var g = this.g_rgb[1].toString(16);
var b = this.g_rgb[2].toString(16);
return '00'.substr(r.length) + r + '00'.substr(g.length) + g + '00'.substr(b.length) + b;
};
BWIPJS.prototype.newpath = function() {
this.g_path = [];
};
BWIPJS.prototype.closepath = function() {
var path = this.g_path;
var plen = path.length;
if (!plen) return;
var f = plen-1;
for ( ; f >= 0 && path[f].op == 'l'; f--);
f++;
if (f < plen-1) {
var poly = [];
var xmin = Infinity;
var ymin = Infinity;
var xmax = -Infinity;
var ymax = -Infinity;
for (var i = f; i < plen; i++) {
var a = path[i];
poly.push([ a.x0, a.y0 ]);
if (xmin > a.x0) xmin = a.x0;
if (xmax < a.x0) xmax = a.x0;
if (ymin > a.y0) ymin = a.y0;
if (ymax < a.y0) ymax = a.y0;
}
var a = path[plen-1];
var b = path[f];
if (a.x1 != b.x0 || a.y1 != b.y0) {
poly.push([ a.x1, a.y1 ]);
if (xmin > a.x1) xmin = a.x1;
if (xmax < a.x1) xmax = a.x1;
if (ymin > a.y1) ymin = a.y1;
if (ymax < a.y1) ymax = a.y1;
}
path.splice(f, plen-f,
{ op:'p', x0:xmin, y0:ymin, x1:xmax, y1:ymax, poly:poly });
} else {
path.push({ op:'c' });
}
};
BWIPJS.prototype.moveto = function(x,y) {
this.g_posx = this.g_tdx + this.g_tsx * x;
this.g_posy = this.g_tdy + this.g_tsy * y;
};
BWIPJS.prototype.rmoveto = function(x,y) {
this.g_posx += this.g_tsx * x;
this.g_posy += this.g_tsy * y;
};
BWIPJS.prototype.lineto = function(x,y) {
var x0 = round(this.g_posx);
var y0 = round(this.g_posy);
this.g_posx = this.g_tdx + this.g_tsx * x;
this.g_posy = this.g_tdy + this.g_tsy * y;
var x1 = round(this.g_posx);
var y1 = round(this.g_posy);
this.g_path.push({ op:'l', x0:x0, y0:y0, x1:x1, y1:y1 });
};
BWIPJS.prototype.rlineto = function(x,y) {
var x0 = round(this.g_posx);
var y0 = round(this.g_posy);
this.g_posx += this.g_tsx * x;
this.g_posy += this.g_tsy * y;
var x1 = round(this.g_posx);
var y1 = round(this.g_posy);
this.g_path.push({ op:'l', x0:x0, y0:y0, x1:x1, y1:y1 });
};
// implements both arc and arcn
BWIPJS.prototype.arc = function(x,y,r,sa,ea,ccw) {
if (sa == ea) {
return;
}
// For now, we only implement full circles...
if (sa != 0 && sa != 360 || ea != 0 && ea != 360) {
throw new Error('arc: not a full circle (' + sa + ',' + ea + ')');
}
x = this.g_tdx + this.g_tsx * x;
y = this.g_tdy + this.g_tsy * y;
// e == ellipse
var rx = r * this.g_tsx;
var ry = r * this.g_tsy;
this.g_path.push({ op:'e', x0:x-rx, y0:y-ry, x1:x+rx, y1:y+ry,
x:x, y:y, rx:rx, ry:ry, sa:sa, ea:ea, ccw:ccw });
};
BWIPJS.prototype.stringwidth = function(str) {
var tsx = this.g_tsx;
var tsy = this.g_tsy;
var size = +this.g_font.FontSize || 10;
// The string can be either a uint8-string or regular string
str = this.toUCS2(this.jsstring(str));
var bbox = this.drawing.measure(str, this.g_font.FontName, size*tsx, size*tsy);
return { w:bbox.width/tsx, h:(bbox.ascent+bbox.descent)/tsy,
a:bbox.ascent/tsy, d:bbox.descent/tsy };
};
BWIPJS.prototype.charpath = function(str, b) {
var sw = this.stringwidth(str);
// Emulate the char-path by placing a rectangle around it
this.rlineto(0, sw.a);
this.rlineto(sw.w, 0);
this.rlineto(0, -sw.h);
};
BWIPJS.prototype.pathbbox = function() {
if (!this.g_path.length) throw new Error('pathbbox: --nocurrentpoint--');
var path = this.g_path;
var llx = Infinity;
var lly = Infinity;
var urx = -Infinity;
var ury = -Infinity;
for (var i = 0; i < path.length; i++) {
var a = path[i];
if (a.op == 'c') {
continue;
}
if (a.x0 < a.x1) {
if (llx > a.x0) llx = a.x0;
if (urx < a.x1) urx = a.x1;
} else {
if (llx > a.x1) llx = a.x1;
if (urx < a.x0) urx = a.x0;
}
if (a.y0 < a.y1) {
if (lly > a.y0) lly = a.y0;
if (ury < a.y1) ury = a.y1;
} else {
if (lly > a.y1) lly = a.y1;
if (ury < a.y0) ury = a.y0;
}
}
// Convert to user-space coordinates
var rv = { llx:(llx-this.g_tdx)/this.g_tsx,
lly:(lly-this.g_tdy)/this.g_tsy,
urx:(urx-this.g_tdx)/this.g_tsx,
ury:(ury-this.g_tdy)/this.g_tsy };
return rv;
};
// Tranforms the pts array to standard (not y-inverted), unscalled values.
BWIPJS.prototype.transform = function(pts) {
var minx = this.minx;
var maxy = this.maxy;
for (var i = 0; i < pts.length; i++) {
var pt = pts[i];
pt[0] = pt[0] - minx;
pt[1] = maxy - pt[1];
}
};
BWIPJS.prototype.stroke = function() {
var tsx = this.g_tsx;
var tsy = this.g_tsy;
var path = this.g_path;
var rgb = this.getRGB();
this.g_path = [];
// This is a "super majority" round i.e. if over .66 round up.
var penw = floor(this.g_penw * tsx + 0.66);
var penh = floor(this.g_penw * tsy + 0.66);
// Calculate the bounding boxes
var nlines = 0, npolys = 0;
for (var i = 0; i < path.length; i++) {
var a = path[i];
if (a.op == 'l') {
// We only stroke vertical and horizontal lines. Complex shapes are
// always filled.
if (a.x0 != a.x1 && a.y0 != a.y1) {
throw new Error('stroke: --not-orthogonal--');
}
var x0 = a.x0;
var y0 = a.y0;
var x1 = a.x1;
var y1 = a.y1;
// Half widths (may be factional)
var penw2 = penw/2;
var penh2 = penh/2;
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
if (x0 == x1) {
this.bbox(x0-penw2, y0, x0+penw-penw2-1, y1); // vertical line
} else {
this.bbox(x0, y0-penh+penh2+1, x1, y1+penh2); // horizontal line
}
nlines++;
} else if (a.op == 'p') {
// Closed (rectangular) poly (border around the barcode)
var minx = Infinity;
var miny = Infinity;
var maxx = -Infinity;
var maxy = -Infinity;
var pts = a.poly;
if (pts.length != 4) {
throw new Error('stroke: --not-a-rect--');
}
for (var i = 0, j = pts.length-1; i < pts.length; j = i++) {
var xj = pts[j][0];
var yj = pts[j][1];
var xi = pts[i][0];
var yi = pts[i][1];
if (xi != xj && yi != yj) {
throw new Error('stroke: --not-orthogonal--');
}
if (xi < minx) minx = xi;
if (xi > maxx) maxx = xi;
if (yi < miny) miny = yi;
if (yi > maxy) maxy = yi;
}
// Half widths (integer)
var penw2 = ceil(penw/2);
var penh2 = ceil(penh/2);
// We render these as two polygons plus a fill.
// When border width is odd, allocate the bigger half to the outside.
this.bbox(minx-penw2, miny-penh2, maxx+penw2, maxy+penh2);
npolys++;
} else {
throw new Error('stroke: --not-a-line--');
}
}
// Draw the lines
var self = this;
this.cmds.push(function() {
// Half widths (big half and remaining half)
var bigw2 = ceil(penw/2);
var bigh2 = ceil(penh/2);
var remw2 = penw - bigw2;
var remh2 = penh - bigh2;
for (var i = 0; i < path.length; i++) {
var a = path[i]
if (a.op == 'l') {
var pts = [ [ a.x0, a.y0 ], [ a.x1, a.y1 ] ];
self.transform(pts);
self.drawing.line(pts[0][0], pts[0][1], pts[1][0], pts[1][1],
a.x0 == a.x1 ? penw : penh, rgb);
self.fill(rgb);
} else {
var pts = a.poly;
self.transform(pts);
var x0 = min(pts[0][0], pts[2][0]);
var x1 = max(pts[0][0], pts[2][0]);
var y0 = min(pts[0][1], pts[2][1]);
var y1 = max(pts[0][1], pts[2][1]);
// Top and left edges are "inside" the polygon.
// Bottom and right edges are outside.
self.drawing.polygon([
[ x0-bigw2, y0-bigh2 ],
[ x0-bigw2, y1+bigh2+1 ],
[ x1+bigw2+1, y1+bigh2+1 ],
[ x1+bigw2+1, y0-bigh2 ]
]);
self.drawing.polygon([
[ x0+remw2, y0+remh2 ],
[ x0+remw2, y1-remh2+1 ],
[ x1-remw2+1, y1-remh2+1 ],
[ x1-remw2+1, y0+remh2 ],
]);
self.drawing.fill(rgb);
}
}
});
};
BWIPJS.prototype.fill = function() {
var path = this.g_path;
var rgb = this.getRGB();
this.g_path = [];
// Calculate the bounding boxes
for (var p = 0; p < path.length; p++) {
var a = path[p];
if (a.op == 'p') { // polygon
var minx = Infinity;
var miny = Infinity;
var maxx = -Infinity;
var maxy = -Infinity;
var pts = a.poly;
for (var i = 0; i < pts.length; i++) {
var xi = pts[i][0];
var yi = pts[i][1];
if (xi < minx) minx = xi;
if (xi > maxx) maxx = xi;
if (yi < miny) miny = yi;
if (yi > maxy) maxy = yi;
}
// With polygons, the right and bottom edges are "outside" and do not
// contribute to the bounding box. But we are in postscript inverted-y
// mode.
this.bbox(minx, miny+1, maxx-1, maxy);
} else if (a.op == 'e') { // ellipse
this.bbox(a.x - a.rx, a.y - a.ry, a.x + a.rx, a.y + a.ry);
} else {
throw new Error('fill: --not-a-polygon--');
}
}
// Render the poly
var self = this;
this.cmds.push(function() {
for (var i = 0; i < path.length; i++) {
var a = path[i];
if (a.op == 'p') {
var pts = a.poly
self.transform(pts);
self.drawing.polygon(pts);
} else if (a.op == 'e') {
var pts = [ [ a.x, a.y ] ];
self.transform(pts);
self.drawing.ellipse(pts[0][0], pts[0][1], a.rx, a.ry, a.ccw);
}
}
self.drawing.fill(rgb);
});
};
BWIPJS.prototype.clip = function() {
var path = this.g_path;
this.g_path = [];
this.g_clip = true;
var self = this;
this.cmds.push(function() {
var polys = [];
for (var i = 0; i < path.length; i++) {
var a = path[i];
if (a.op == 'p') {
var pts = a.poly
self.transform(pts);
polys.push(pts);
} else {
throw new Error('clip: only polygon regions supported');
}
}
self.drawing.clip(polys);
});
};
// The pix array is in standard (not y-inverted postscript) orientation.
BWIPJS.prototype.maxicode = function(pix) {
var tsx = this.g_tsx;
var tsy = this.g_tsy;
var rgb = this.getRGB();
// Module width. Module height is an integer multiple of tsy.
var twidth = 1.04 * tsx * 100;
var mwidth = (twidth / 30)|0;
if (twidth - (mwidth*30-1) > 9) {
mwidth++;
}
// Dimensions needed for plotting the hexagons. These must be integer values.
var w, h, wgap, hgap;
// if (opts.??? ) {
// // Create a one or two pixel gap
// wgap = (mwidth & 1) ? 1 : 2;
// hgap = 1;
// w = mwidth - gap;
// h = 4 * tsy;
// } else {
// Create a 1/8mm gap
wgap = (tsx/2)|0;
hgap = (tsy/2)|0;
w = mwidth - wgap;
if (w & 1) {
w--;
}
h = ((4*tsy)|0) - hgap;
//}
// These must be integer values
var w2 = w / 2 - 1; // half width
var qh = ((w2+1) / 2)|0; // quarter height
var vh = h - 2 - 2 * qh; // side height
// Bounding box
this.bbox(0, 0, mwidth*30 - wgap, tsy * 3 * 32 + tsy * 4 - hgap);
// Render the elements
var self = this;
this.cmds.push(function() {
// Draw the hexagons
for (var i = 0; i < pix.length; i++) {
var c = pix[i];
var x = c % 30;
var y = (c / 30)|0;
// Adjust x,y to the top of hexagon
x *= mwidth;
x += (y & 1) ? mwidth : mwidth/2;
x = x|0;
y = 33 - y; // invert for postscript notation
y *= tsy * 3;
y += tsy * 2 - h/2;
y = y|0;
// Build bottom up so the drawing is top-down.
var pts = [ [ x-0.5, y-- ] ];
y -= qh-1;
pts.push([x-1-w2, y--]);
y -= vh;
pts.push([x-1-w2, y--]);
y -= qh-1;
pts.push([x-0.5, y++]);
y += qh-1;
pts.push([x+w2, y++]);
y += vh;
pts.push([x+w2, y++]);
self.transform(pts);
self.drawing.hexagon(pts, rgb);
}
self.drawing.fill(rgb);
// Draw the rings
var x = (14 * mwidth + mwidth/2 + 0.01)|0;
var y = ((12 * 4 + 3) * tsy - qh/2 + 0.01)|0;
self.drawing.ellipse(x, y, (0.5774*3.5*tsx+0.01)|0, (0.5774*3.5*tsy+0.01)|0, true);
self.drawing.ellipse(x, y, (1.3359*3.5*tsx+0.01)|0, (1.3359*3.5*tsy+0.01)|0, false);
self.drawing.fill(rgb);
self.drawing.ellipse(x, y, (2.1058*3.5*tsx+0.01)|0, (2.1058*3.5*tsy+0.01)|0, true);
self.drawing.ellipse(x, y, (2.8644*3.5*tsx+0.01)|0, (2.8644*3.5*tsy+0.01)|0, false);
self.drawing.fill(rgb);
self.drawing.ellipse(x, y, (3.6229*3.5*tsx+0.01)|0, (3.6229*3.5*tsy+0.01)|0, true);
self.drawing.ellipse(x, y, (4.3814*3.5*tsx+0.01)|0, (4.3814*3.5*tsy+0.01)|0, false);
self.drawing.fill(rgb);
});
};
// UTF-8 to UCS-2 (no surrogates)
BWIPJS.prototype.toUCS2 = function(str) {
return str.replace(/[\xc0-\xdf][\x80-\xbf]|[\xe0-\xff][\x80-\xbf]{2}/g,
function(s) {
var code;
if (s.length == 2) {
code = ((s.charCodeAt(0)&0x1f)<<6)|
(s.charCodeAt(1)&0x3f);
} else {
code = ((s.charCodeAt(0)&0x0f)<<12)|
((s.charCodeAt(1)&0x3f)<<6)|
(s.charCodeAt(2)&0x3f);
}
return String.fromCharCode(code);
});
};
// dx,dy are inter-character gaps
BWIPJS.prototype.show = function(str, dx, dy) {
if (!str.length) {
return;
}
// Capture current graphics state
var tsx = this.g_tsx;
var tsy = this.g_tsy;
var name = this.g_font.FontName || 'OCR-B';
var size = (this.g_font.FontSize || 10);
var szx = size * tsx;
var szy = size * tsy;
var posx = this.g_posx;
var posy = this.g_posy;
var rgb = this.getRGB();
// The string can be either a uint8-string or regular string.
str = this.toUCS2(this.jsstring(str));
// Convert dx,dy to device space
dx = tsx * dx || 0;
dy = tsy * dy || 0;
// Bounding box.
var base = posy + dy;
var bbox = this.drawing.measure(str, name, szx, szy);
var width = bbox.width + (str.length-1) * dx;
this.bbox(posx, base-bbox.descent+1, posx+width-1, base+bbox.ascent);
this.g_posx += width;
var self = this;
self.cmds.push(function() {
// self.transform()
var x = posx - self.minx;
var y = self.maxy - posy;
self.drawing.text(x, y, str, rgb, { name:name, width:szx, height:szy, dx:dx });
});
};
// drawing surface bounding box
BWIPJS.prototype.bbox = function(x0, y0, x1, y1) {
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
x0 = floor(x0);
y0 = floor(y0);
x1 = ceil(x1);
y1 = ceil(y1);
if (this.minx > x0) this.minx = x0;
if (this.maxx < x1) this.maxx = x1;
if (this.miny > y0) this.miny = y0;
if (this.maxy < y1) this.maxy = y1;
};
BWIPJS.prototype.render = function() {
if (this.minx === Infinity) {
// Most likely, `dontdraw` was set in the options
return false;
}
// Draw the image
this.drawing.init(this.maxx - this.minx + 1, this.maxy - this.miny + 1,
this.g_tsx, this.g_tsy);
for (var i = 0, l = this.cmds.length; i < l; i++) {
this.cmds[i]();
}
return this.drawing.end();
};
return BWIPJS;
})(); // BWIPJS closure
// drawing-builtin.js
//
// The aliased (except the fonts) graphics used by drawing-canvas.js and
// drawing-zlibpng.js
//
// All x,y and lengths are integer values.
//
// For the methods that take a color `rgb` parameter, the value is always a
// string with format RRGGBB.
function DrawingBuiltin() {
var floor = Math.floor;
// Unrolled x,y rotate/translate matrix
var tx0 = 0, tx1 = 0, tx2 = 0, tx3 = 0;
var ty0 = 0, ty1 = 0, ty2 = 0, ty3 = 0;
var opts; // see setopts()
var gs_image, gs_rowbyte; // rowbyte will be 1 for png's, 0 for canvas
var gs_width, gs_height; // image size, in pixels
var gs_dx, gs_dy; // x,y translate (padding)
var gs_r, gs_g, gs_b; // rgb
var gs_xymap; // edge map
var gs_xyclip; // clip region map (similar to xymap)
return {
// setopts() is called after the options are fixed-up/normalized,
// but before calling into BWIPP.
// This method allows omitting the options in the constructor call.
// The method is optional.
setopts(options) {
opts = options;
},
// Ensure compliant bar codes by always using integer scaling factors.
scale : function(sx, sy) {
// swissqrcode requires clipping and drawing that are not scaled to the
// the barcode module size.
if (opts.bcid == 'swissqrcode') {
return [ sx, sy ];
} else {
return [ (sx|0)||1, (sy|0)||1 ];
}
},
// Measure text. This and scale() are the only drawing primitives that
// are called before init().
//
// `font` is the font name typically OCR-A or OCR-B.
// `fwidth` and `fheight` are the requested font cell size. They will
// usually be the same, except when the scaling is not symetric.
measure : function(str, font, fwidth, fheight) {
fwidth = fwidth|0;
fheight = fheight|0;
var fontid = FontLib.lookup(font);
var width = 0;
var ascent = 0;
var descent = 0;
for (var i = 0, l = str.length; i < l; i++) {
var ch = str.charCodeAt(i);
var glyph = FontLib.getglyph(fontid, ch, fwidth, fheight);
ascent = Math.max(ascent, glyph.top);
descent = Math.max(descent, glyph.height - glyph.top);
if (i == l-1) {
width += glyph.left + glyph.width;
} else {
width += glyph.advance;
}
}
return { width:width, ascent:ascent, descent:descent };
},
// width and height represent the maximum bounding box the graphics will occupy.
// The dimensions are for an unrotated rendering. Adjust as necessary.
init : function(width, height) {
// Add in the effects of padding. These are always set before the
// drawing constructor is called.
var padl = opts.paddingleft;
var padr = opts.paddingright;
var padt = opts.paddingtop;
var padb = opts.paddingbottom;
var rot = opts.rotate || 'N';
width += padl + padr;
height += padt + padb;
if (+opts.sizelimit && +opts.sizelimit < width * height) {
throw new Error('Image size over limit');
}
// Transform indexes are: x, y, w, h
switch (rot) {
// tx = w-y, ty = x
case 'R': tx1 = -1; tx2 = 1; ty0 = 1; break;
// tx = w-x, ty = h-y
case 'I': tx0 = -1; tx2 = 1; ty1 = -1; ty3 = 1; break;
// tx = y, ty = h-x
case 'L': tx1 = 1; ty0 = -1; ty3 = 1; break;
// tx = x, ty = y
default: tx0 = ty1 = 1; break;
}
// Setup the graphics state
var swap = rot == 'L' || rot == 'R';
gs_width = swap ? height : width;
gs_height = swap ? width : height;
gs_dx = padl;
gs_dy = padt;
gs_xymap = [];
gs_xymap.min = Infinity;
gs_xyclip = null;
gs_r = gs_g = gs_b = 0;
// Get the rgba image from the constructor
var res = this.image(gs_width, gs_height);
gs_image = res.buffer;
gs_rowbyte = res.ispng ? 1 : 0;
},
// Unconnected stroked lines are used to draw the bars in linear barcodes;
// and the border around a linear barcode (e.g. ITF-14)
// No line cap should be applied. These lines are always orthogonal.
line : function(x0, y0, x1, y1, lw, rgb) {
x0 = x0|0;
y0 = y0|0;
x1 = x1|0;
y1 = y1|0;
// Most linear barcodes, the line width will be integral. The exceptions
// are variable width barcodes (e.g. code39) and the postal 4-state codes.
lw = Math.round(lw) || 1;
if (y1 < y0) { var t = y0; y0 = y1; y1 = t; }
if (x1 < x0) { var t = x0; x0 = x1; x1 = t; }
gs_r = parseInt(rgb.substr(0,2), 16);
gs_g = parseInt(rgb.substr(2,2), 16);
gs_b = parseInt(rgb.substr(4,2), 16);
// Horizontal or vertical line?
var w2 = (lw/2)|0;
if (x0 == x1) {
// Vertical line
x0 = x0 - lw + w2; // big half
x1 = x1 + w2 - 1; // small half
} else {
// Horizontal line (inverted halves)
y0 = y0 - w2;
y1 = y1 + lw - w2 - 1;
}
for (var y = y0; y <= y1; y++) {
for (var x = x0; x <= x1; x++) {
set(x, y, 255);
}
}
},
// Polygons are used to draw the connected regions in a 2d barcode.
// These will always be unstroked, filled, orthogonal shapes.
//
// You will see a series of polygon() calls, followed by a fill().
polygon : function(pts) {
var npts = pts.length;
for (var j = npts-1, i = 0; i < npts; j = i++) {
if (pts[j][0] == pts[i][0]) {
// Vertical lines do not get their end points. End points
// are added by the horizontal line logic.
var xj = pts[j][0]|0; // i or j, doesn't matter
var yj = pts[j][1]|0;
var yi = pts[i][1]|0;
if (yj > yi) {
for (var y = yi+1; y < yj; y++) {
addPoint(xj, y);
}
} else {
for (var y = yj+1; y < yi; y++) {
addPoint(xj, y);
}
}
} else {
var xj = pts[j][0]|0;
var xi = pts[i][0]|0;
var yj = pts[j][1]|0; // i or j, doesn't matter
// Horizontal lines are tricky. As a rule, top lines get filled,
// bottom lines do not (similar to how left edges get filled and
// right edges do not).
//
// Where it gets complex is deciding whether the line actually
// adds edges. There are cases where a horizontal line does
// not add anything to the scanline plotting. And it doesn't
// actually matter whether the line is a top or bottom edge,
// the logic is the same.
//
// A left edge is added if the edge to its left is below.
// A right edge is added if the edge to its right is below.
if (xj < xi) {
var yl = pts[j == 0 ? npts-1 : j-1][1]; // left edge
var yr = pts[i == npts-1 ? 0 : i+1][1]; // right edge
if (yl > yj) {
addPoint(xj, yj);
}
if (yr > yj) {
addPoint(xi, yj);
}
} else {
var yl = pts[i == npts-1 ? 0 : i+1][1]; // left edge
var yr = pts[j == 0 ? npts-1 : j-1][1]; // right edge
if (yl > yj) {
addPoint(xi, yj);
}
if (yr > yj) {
addPoint(xj, yj);
}
}
}
}
},
// An unstroked, filled hexagon used by maxicode. You can choose to fill
// each individually, or wait for the final fill().
//
// The hexagon is drawn from the top, counter-clockwise.
//
// The X-coordinate for the top and bottom points on the hexagon is always
// .5 pixels. We draw our hexagons with a 2 pixel flat top.
//
// All other points of the polygon/hexagon are guaranteed to be integer values.
hexagon : function(pts, rgb) {
var x = pts[0][0]|0;
var y = pts[0][1]|0;
var qh = (pts[1][1] - pts[0][1])|0; // height of triangle (quarter height)
var vh = (pts[2][1] - pts[1][1] - 1)|0; // height of vertical side
var xl = (pts[2][0])|0; // left side
var xr = (pts[4][0])|0; // right side
gs_r = parseInt(rgb.substr(0,2), 16);
gs_g = parseInt(rgb.substr(2,2), 16);
gs_b = parseInt(rgb.substr(4,2), 16);
fillSegment(x, x+1, y++);
for (var k = 1; k < qh; k++) {
fillSegment(x-2*k, x+1+2*k, y++);
}
for (var k = 0; k <= vh; k++) {
fillSegment(xl, xr, y++);
}
for (var k = qh-1; k >= 1; k--) {
fillSegment(x-2*k, x+1+2*k, y++);
}
fillSegment(x, x+1, y);
},
// An unstroked, filled ellipse. Used by dotcode and maxicode at present.
// maxicode issues pairs of ellipse calls (one cw, one ccw) followed by a fill()
// to create the bullseye rings. dotcode issues all of its ellipses then a
// fill().
ellipse : function(x, y, rx, ry, ccw) {
drawEllipse((x-rx)|0, (y-ry)|0, (x+rx)|0, (y+ry)|0, ccw);
},
// PostScript's default fill rule is non-zero but since there are never
// intersecting regions, we use the easier to implement even-odd.
fill : function(rgb) {
gs_r = parseInt(rgb.substr(0,2), 16);
gs_g = parseInt(rgb.substr(2,2), 16);
gs_b = parseInt(rgb.substr(4,2), 16);
evenodd();
gs_xymap = [];
gs_xymap.min = Infinity;
},
// Currently only used by swissqrcode. The `polys` area is an array of
// arrays of points. Each array of points is identical to the `pts`
// parameter passed to polygon(). The postscript default clipping rule,
// like the fill rule, is even-odd winding.
clip : function(polys) {
if (!gs_xyclip) {
gs_xyclip = [];
gs_xyclip.min = Infinity;
}
// Swap out the xymap for the clip map so addPoint() works on it.
var xymap = gs_xymap;
gs_xymap = gs_xyclip;
// Now just use the polygon() logic to fill in the clipping regions.
for (var i = 0, l = polys.length; i < l; i++) {
this.polygon(polys[i]);
}
// Restore
gs_xymap = xymap;
},
unclip : function() {
gs_xyclip = null;
},
// Draw text with optional inter-character spacing. `y` is the baseline.
// font is an object with properties { name, width, height, dx }
// width and height are the font cell size.
// dx is extra space requested between characters (usually zero).
text : function(x, y, str, rgb, font) {
x = x|0;
y = y|0;
gs_r = parseInt(rgb.substr(0,2), 16);
gs_g = parseInt(rgb.substr(2,2), 16);
gs_b = parseInt(rgb.substr(4,2), 16);
var fontid = FontLib.lookup(font.name);
var fwidth = font.width|0;
var fheight = font.height|0;
var dx = font.dx|0;
for (var k = 0; k < str.length; k++) {
var ch = str.charCodeAt(k);
var glyph = FontLib.getglyph(fontid, ch, fwidth, fheight);
var gt = y - glyph.top;
var gl = glyph.left;
var gw = glyph.width;
var gh = glyph.height;
var gb = glyph.bytes;
var go = glyph.offset; // offset into bytes
for (var i = 0; i < gw; i++) {
for (var j = 0; j < gh; j++) {
var a = gb[go + j * gw + i];
if (a) {
set(x+gl+i, gt+j, a);
}
}
}
x += glyph.advance + dx;
}
},
// Called after all drawing is complete.
end : function() {
},
};
// This code is specialized to deal with two types of RGBA buffers:
// - canvas style, which is true RGBA
// - PNG style, which has a one-byte "filter code" prefixing each row.
function set(x, y, a) {
if (gs_xyclip && clipped(x, y)) {
return;
}
// translate/rotate