UNPKG

@bwip-js/browser

Version:

JavaScript barcode generator supporting over 100 types and standards.

1,366 lines (1,256 loc) 188 kB
// 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