bwip-angular2
Version:
JavaScript barcode generator supporting over 90 types and standards.
609 lines (562 loc) • 17.5 kB
JavaScript
// file : bwip-js/bwipjs.js
//
// Graphics-context interface to the BWIPP cross-compiled code
// Math.floor(), etc. are notoriously slow. Caching seems to help.
var floor = Math.floor;
var round = Math.round;
var ceil = Math.ceil;
// fontlib : fixedfont or freetype
function BWIPJS(fontlib, monochrome) {
if (this.constructor !== BWIPJS) {
return new BWIPJS(fontlib, monochrome);
}
this.bmap = null;// Bitmap interface
this.gstk = []; // Graphics save/restore stack
this.cmds = []; // Graphics primitives to replay when rendering
this.rgbmap = {}; // Unique RGB entries used with the fonts
this.ncolors = 0; // Number of unique RGBA entries
this.fontlib = fontlib;
this.reset();
// Bounding box
this.minx = this.miny = Infinity;
this.maxx = this.maxy = 0;
fontlib.monochrome(monochrome);
}
BWIPJS.prototype.bitmap = function(bitmap) {
if (bitmap) {
this.bmap = bitmap;
}
return this.bmap;
}
// 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)
}
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();
for (var id in ctx) {
this[id] = ctx[id];
}
// Color is part of the bitmap interface and must be restored separately.
var self = this;
var r = this.g_rgb[0], g = this.g_rgb[1], b = this.g_rgb[2];
this.cmds.push(function() {
self.bmap.color(r, g, b);
});
}
// 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.findfont = function(name) {
return { FontName:name };
}
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;
}
BWIPJS.prototype.setlinewidth = function(w) {
this.g_penw = w;
}
BWIPJS.prototype.setfont = function(f) {
this.g_font = f;
}
BWIPJS.prototype.getfont = function() {
return this.fontlib.lookup(this.g_font.FontName.toString());
}
// Special function to replace setanycolor in BWIPP
// Takes a string of hex digits either 6 chars in length (rrggbb) or
// 8 chars (ccmmyykk).
BWIPJS.prototype.setcolor = function(s) {
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);
} else if (s.length == 8) {
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);
} else {
throw 'bwipp.setcolor: invalid string length (' + s + ')' ;
}
// Set the color in the bitmap
var self = this;
self.cmds.push(function() {
self.bmap.color(r, g, b);
});
this.g_rgb = [ r, g, b ];
}
BWIPJS.prototype.newpath = function() {
this.g_path = [];
}
BWIPJS.prototype.closepath = function() {
if (this.g_path.length) {
var c0 = this.g_path[0];
var c1 = this.g_path[this.g_path.length-1];
this.g_path.push([ c1[0], c1[1] ]);
this.g_path.push(['c']);
this.g_path.push([ c0[0], c0[1] ]);
}
}
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) {
this.g_path.push([this.g_posx, this.g_posy]);
this.g_path.push(['l']);
this.g_posx = this.g_tdx + this.g_tsx * x;
this.g_posy = this.g_tdy + this.g_tsy * y;
this.g_path.push([this.g_posx, this.g_posy]);
}
BWIPJS.prototype.rlineto = function(x,y) {
this.g_path.push([this.g_posx, this.g_posy]);
this.g_path.push(['l']);
this.g_posx += this.g_tsx * x;
this.g_posy += this.g_tsy * y;
this.g_path.push([this.g_posx, this.g_posy]);
}
BWIPJS.prototype.stringwidth = function(str) {
var font = this.getfont();
var size = (+this.g_font.FontSize || 10) * this.g_tsx;
// str may be a uint8-string or normal string
var cca = typeof str === 'string';
// Width, ascent, and descent of the char-path.
// Font metrics are always available.
var w = 0, a = 0, d = 0;
for (var i = 0; i < str.length; i++) {
var cd = cca ? str.charCodeAt(i) : str[i];
var glyph = this.fontlib.getglyph(font, cd, size, size); // no y-scaling
w += glyph.advance;
a = Math.max(a, glyph.top);
d = Math.max(d, glyph.height-glyph.top);
}
return { w:w/this.g_tsx, h:(a+d)/this.g_tsy,
a:a/this.g_tsy, d:d/this.g_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 pth = this.g_path;
var llx = pth[0][0];
var lly = pth[0][1];
var urx = 0;
var ury = 0;
for (var i = 2, inc = 2; i < pth.length; i += inc) {
if (llx > pth[i][0]) llx = pth[i][0];
if (urx < pth[i][0]) urx = pth[i][0];
if (lly > pth[i][1]) lly = pth[i][1];
if (ury < pth[i][1]) ury = pth[i][1];
inc = (inc == 2 ? 1 : 2);
}
// 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;
}
BWIPJS.prototype.stroke = function() {
var self = this;
var penx = this.g_penw*this.g_tsx;
var peny = this.g_penw*this.g_tsy;
var path = this.g_path;
var segs = this.g_path.length / 3; // number of line segments
if (this.g_path[this.g_path.length-2][0] == 'c')
segs--;
// Track number of colors
var rgb = (this.g_rgb[0] << 16) | (this.g_rgb[1] << 8) | this.g_rgb[2];
if (!this.rgbmap[rgb]) {
this.rgbmap[rgb] = 1;
this.ncolors += 1;
}
// Calcuate the bounding boxes
for (var i = 0; i < path.length; ) {
var s = path[i++]; // start point
var a = path[i++]; // opcode
var e = path[i++]; // end point
if (a[0] == 'l') {
this.linebbox(s[0], s[1], e[0], e[1], penx, peny);
}
}
// Render the lines
this.cmds.push(function() {
// Draw the lines
for (var i = 0; i < path.length; ) {
var s = path[i++]; // start point
var a = path[i++]; // opcode
var e = path[i++]; // end point
if (a[0] == 'l') {
self.drawline(true, s[0], s[1], e[0], e[1], penx, peny, segs > 1);
}
}
});
this.g_path = [];
}
// Fix sources of rounding error by making the scale-factors integral.
// Currently, only floor is being used.
BWIPJS.prototype.floorscale = function() {
this.g_tsx = floor(this.g_tsx) || 1;
this.g_tsy = floor(this.g_tsy) || 1;
}
BWIPJS.prototype.ceilscale = function() {
this.g_tsx = ceil(this.g_tsx) || 1;
this.g_tsy = ceil(this.g_tsy) || 1;
}
BWIPJS.prototype.roundscale = function() {
this.g_tsx = round(this.g_tsx) || 1;
this.g_tsy = round(this.g_tsy) || 1;
}
// source is an 8-bit bitmask
// This implementation is optimized for 2D bar codes. That is, it does not
// distort the image due to rounding errors. Every pixel is scaled
// identically, so the resulting image may be smaller by a few pixels than
// the scaling factor would require. The transform matrix is not used.
BWIPJS.prototype.imagemask = function(width, height, source) {
var tx = floor(this.g_tdx);
var ty = floor(this.g_tdy);
var dx = floor(this.g_tsx / width); // pixel width
var dy = floor(this.g_tsy / height); // pixel height
var rl = ceil(width / 8); // row length (bytes per row)
var y0 = ty + height * dy;
var x0;
var self = this;
if (!dx || !dy) {
throw new Error('Image scaled to zero size.');
}
// Track number of colors
var rgb = (this.g_rgb[0] << 16) | (this.g_rgb[1] << 8) | this.g_rgb[2];
if (!this.rgbmap[rgb]) {
this.rgbmap[rgb] = 1;
this.ncolors += 1;
}
this.bbox(tx, ty, dx * width, dy * height);
this.cmds.push(function() {
var minx = self.minx;
var miny = self.miny;
for (var y = 0; y < height; y++) {
x0 = tx;
y0 -= dy;
for (var x = 0; x < width; x++) {
var by = source[y*rl + (x>>>3)];
var bt = by & (1 << 7-(x&7));
if (bt) {
var x1 = x0 + dx;
var y1 = y0 + dy;
for (var j = y0; j < y1; j++) {
for (var k = x0; k < x1; k++) {
self.bmap.set(k-minx,j-miny,255);
}
}
}
x0 += dx;
}
}
});
}
// dx,dy are inter-character gaps
BWIPJS.prototype.show = function(str, dx, dy) {
if (!str.length) {
return;
}
// Capture current graphics state
var self = this;
var font = self.getfont();
var size = +self.g_font.FontSize || 10;
var tsx = self.g_tsx;
var tsy = self.g_tsy;
var posx = floor(self.g_posx);
var posy = floor(self.g_posy);
var r = self.g_rgb[0];
var g = self.g_rgb[1];
var b = self.g_rgb[2];
var szx = floor(size * tsx);
var szy = floor(size * tsy);
// The string can be either a uint8-string or regular string
var cca = typeof str === 'string';
// Convert dx,dy to device space
dx = floor(self.g_tsx * dx);
dy = floor(self.g_tsy * dy);
// Estimate number of colors
var rgb = (r << 16) | (g << 8) | b;
var nalpha = self.rgbmap[rgb];
if (!nalpha) {
self.ncolors += 256;
} else if (nalpha == 1) {
self.ncolors += 255;
}
self.rgbmap[rgb] = 256;
// Bounding box. Use the actual glyph sizes.
var base = floor(self.g_posy) + dy; // loop invariant
for (var i = 0; i < str.length; i++) {
var ch = cca ? str.charCodeAt(i) : str[i];
var glyph = self.fontlib.getglyph(font, ch, szx, szx); // no y-scaling
// The OCR glyphs seem to be about a point right compared to the
// font metrics hard-coded into BWIPP. This is especially apparent
// in the EAN and UPC codes where the bars mix with the text.
var left = floor(this.g_posx) + glyph.left;
if (font <= 1) { // && ch >= 48 && ch <= 57) {
left -= floor(1.0 * tsx);
}
self.bbox(left, base+glyph.top-glyph.height, glyph.width, glyph.height);
this.g_posx += glyph.advance + dx;
}
this.g_posx -= dx; // overshot
this.maxicount = 0;
self.cmds.push(function() {
var minx = self.minx;
var miny = self.miny;
// PostScript renders bottom-up, so we must render the glyphs inverted.
for (var i = 0; i < str.length; i++) {
var ch = cca ? str.charCodeAt(i) : str[i];
var glyph = self.fontlib.getglyph(font, ch, szx, szx); // no y-scaling
// The OCR glyphs seem to be about a point right compared to the
// font metrics hard-coded into BWIPP. This is especially apparent
// in the EAN and UPC codes where the bars mix with the text.
var top = posy + glyph.top + dy - 1;
var left = posx + glyph.left;
if (font <= 1) { // && ch >= 48 && ch <= 57) {
left -= floor(1.0 * tsx);
}
var w = glyph.width;
var h = glyph.height;
var b = glyph.bytes;
var o = glyph.offset; // offset into bytes
for (var x = 0; x < w; x++) {
for (var y = 0; y < h; y++) {
var a = b[o + y * w + x];
if (a)
self.bmap.set(left+x-minx, top-y-miny, a);
}
}
posx += glyph.advance + dx;
}
});
}
// Line algorithm that produces a more symmetric line than Bresenham's
//
// optmz == boolean
// x1,y1 == starting coordinates
// x2,y2 == ending coordinates
// penx,peny == pen dimensions
// merge == multi-line : merge the end points
//
// When optmz is true, we use the fast vertical/horizontal line drawing
// optimizations. This works well for single lines.
// When optmz is false, we always use the arbitrary line drawing alg, as
// it better connects one line with the next.
BWIPJS.prototype.drawline = function(optmz, x1, y1, x2, y2, penx, peny, merge) {
var minx = this.minx;
var miny = this.miny;
if (optmz && (x1 == x2 || y1 == y2)) {
var lx = round(penx);
var ly = round(peny);
if (y2 < y1) { var t = y1; y1 = y2; y2 = t; }
if (x2 < x1) { var t = x1; x1 = x2; x2 = t; }
// Horizontal or vertical line?
if (x1 == x2) {
// Vertical line
x1 = floor(x1 - lx/2);
x2 = floor(x2 + lx/2);
y1 = floor(y1 - (merge ? ly/2 : 0));
y2 = floor(y2 + (merge ? ly/2 : 0));
} else {
// Horizontal line
y1 = floor(y1 - ly/2);
y2 = floor(y2 + ly/2);
x1 = floor(x1 - (merge ? lx/2 : 0));
x2 = floor(x2 + (merge ? lx/2 : 0));
}
for (var y = y1; y < y2; y++) {
for (var x = x1; x < x2; x++) {
this.bmap.set(x-minx,y-miny,255);
}
}
return;
}
// Draw an arbitrary line
x1 = floor(x1);
x2 = floor(x2);
y1 = floor(y1);
y2 = floor(y2);
var du = Math.abs(x2-x1);
var dv = Math.abs(y2-y1);
var kx = (x2 < x1 ? -1 : 1);
var ky = (y2 < y1 ? -1 : 1);
var x = x1;
var y = y1;
var d = 0;
// Calculate the effect of pen width
var penw = floor(Math.sqrt(penx*penx + peny*peny));
var pixh = round(Math.sqrt((penw*penw)/((dv*dv)/(du*du)+1))) || 1;
var pixw = round(Math.sqrt(penw*penw-pixh*pixh)) || 1;
if (du >= dv) {
// Increment on x
while (x != x2) {
for (var j = 0; j < pixh; j++) {
this.bmap.set(x-minx, y+j-miny, 255);
}
d += dv;
if (d >= du) {
d -= du;
y += ky;
}
x += kx;
}
for (var j = 0; j < pixh; j++) {
this.bmap.set(x-minx, y+j-miny, 255);
}
} else {
// Increment on y
while (y != y2) {
for (var j = 0; j < pixw; j++) {
this.bmap.set(x+j-minx, y-miny, 255);
}
d += du;
if (d >= dv) {
d -= dv;
x += kx;
}
y += ky;
}
for (var j = 0; j < pixw; j++) {
this.bmap.set(x+j-minx, y-miny, 255);
}
}
} // end of drawline()
// Bounding box for a line. The renderers only draw orthogonal lines.
// Maxicode is drawn using a font.
BWIPJS.prototype.linebbox = function(x0, y0, x1, y1, penw, penh) {
if (x0 > x1) {
var t = x0;
x0 = x1;
x1 = t;
}
if (y0 > y1) {
var t = y0;
y0 = y1;
y1 = t;
}
penw = round(penw);
penh = round(penh);
if (x0 == x1) {
// vertical line
var xl = floor(x0 - penw/2);
var xr = floor(x0 + penw/2);
this.bbox(floor(xl), floor(y0), xr-xl, floor(y1-y0));
} else if (y0 == y1) {
// horizontal line
this.bbox(x0, y0 - penh/2, x1-x0, penh);
} else {
this.bbox(x0, y0, x1-x0, y1-y0);
}
}
// Track the bounding box of the image
BWIPJS.prototype.bbox = function(x, y, w, h) {
if (this.minx > x) {
this.minx = x;
}
if (this.maxx < x + w - 1) {
this.maxx = x + w - 1;
}
if (this.miny > y) {
this.miny = y;
}
if (this.maxy < y + h - 1) {
this.maxy = y + h - 1;
}
}
BWIPJS.prototype.render = function(callback) {
this.minx = floor(this.minx);
this.miny = floor(this.miny);
this.maxx = floor(this.maxx);
this.maxy = floor(this.maxy);
// Tell the bitmap about the image
if (this.bmap.imageinfo) {
this.bmap.imageinfo(this.maxx - this.minx + 1,
this.maxy - this.miny + 1,
this.ncolors);
}
// Draw the image to the bitmap
for (var i = 0, l = this.cmds.length; i < l; i++) {
this.cmds[i]();
}
if (this.bmap.finalize) {
this.bmap.finalize(callback);
}
}
BWIPJS.VERSION = '1.7.2 (2019-03-11)';
if (typeof module === 'object' && module.exports) {
module.exports = BWIPJS;
}