UNPKG

bwip-js

Version:

JavaScript barcode generator supporting over 100 types and standards.

221 lines (209 loc) 9.32 kB
// bwip-js/examples/drawing-example.js // // Example use of the drawing interface. This code is for expository purposes only. // Using the HTML5 canvas API creates "fuzzy" barcodes which are anathema // to reliable scanning. // // For the methods that take a color `rgb` parameter, the value is always a // string with format RRGGBB. Internally, BWIPP accepts both RGB and CMYK values // but bwip-js always converts CMYK to RGB before the drawing code sees it. // The signature of this factory constructor is up to you. It is your code that // calls it and passes the returned drawing instance to `bwipjs.render()`. // See example.html. function DrawingExample(opts, canvas) { let ctx = canvas.getContext('2d', { willReadFrequently:true }); // PostScript transparently creates compound path regions. // We must do it explicitly with canvas. let compound = null; return { // setopts() is called after the options are fixed-up/normalized, // but before calling into BWIPP. // // This method allows omitting the options object in the constructor call, // which simplifies the pattern: // // bwipjs.render({ bcid:'code128', ... }, myDrawing()); // // In the above, it is awkward to pass the options object to the drawing // constructor. // // The method is optional. Implemented in v4.0. setopts(options) { opts = options; }, // Adjust scale. The return value is a two-element array with the // scale-x and scale-y values adjusted as desired. // // For this example, we want fractions of pixels, so do nothing. // The builtin drawing returns [ floor(sx), floor(sy) ] to ensure all // bars and spaces are sized uniformly. // // Composite symbols cause this method to be called multiple times; be // consistent if you adjust the values. scale(sx, sy) { return null; }, // Measure text. measure() and scale() are the only drawing primitives // called before init(). // // `font` is the font name, typically Helvetica, Courier or OCR-A. // The bwip-js builtin drawing maps both Helvetica and Courier to OCR-B. // // The build tools pre-process the PostScript and change the text above // the ISBN, ISMN, ISSN barcodes to default to OCR-A. // // The user can explicitly change the font name via the BWIPP text options. // // `width` and `height` are the requested font cell size. They will // usually be the same, except when the x/y scaling is not symmetric. measure(str, font, width, height) { // The canvas measure api is basically useless, especially when dealing // with asymmetric scaling. The best we can hope for is a rough // approximation... ctx.font = height + 'px monospace'; let bbox = ctx.measureText(str); // The return is an object with properties { width, ascent, descent }. // All values in pixels. let descent = /[Qgjpqy]/.test(str) ? 0.25 * height : 0; return { width:bbox.width * width / height, ascent:0.65 * height, descent:descent }; }, // Initialize the drawing surface. // `width` and `height` represent the maximum bounding box the graphics will occupy. // The dimensions are for an unrotated rendering. Adjust as necessary. init(width, height) { // Add in the effects of padding let padl = opts.paddingleft; let padr = opts.paddingright; let padt = opts.paddingtop; let padb = opts.paddingbottom; width += padl + padr; height += padt + padb; // Set up the transform. The values in the arrays are: // [0] : swap width/height dimensions flag // [1],[2] : dx,dy translate needed with padding-left and padding-top // [3] : rotation (multiple of PI) let tx = { R:[ 1, height-padt, padl, 0.5 ], L:[ 1, padt, width-padl, 1.5 ], I:[ 0, width-padl, height-padt, 1 ] }[opts.rotate] || [ 0, padl, padt, 0 ]; canvas.width = tx[0] ? height : width; canvas.height = tx[0] ? width : height; ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset to unity transform // Set background before the transform makes this tricky. if (/^[0-9a-fA-F]{6}$/.test(''+opts.backgroundcolor)) { ctx.fillStyle = '#' + opts.backgroundcolor; ctx.fillRect(0, 0, canvas.width, canvas.height); } else { ctx.clearRect(0, 0, canvas.width, canvas.height); } // Apply the transform ctx.translate(tx[1], tx[2]); ctx.rotate(tx[3] * Math.PI); }, // Unconnected stroked lines are used to draw the bars in linear barcodes. // No line cap should be applied. These lines are always orthogonal. line(x0, y0, x1, y1, linew, rgb) { ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.lineWidth = linew; ctx.lineCap = 'butt'; ctx.strokeStyle = '#' + rgb; ctx.stroke(); }, // Polygons are used to draw the connected regions in a 2d barcode. // These will always be unstroked, filled, non-intersecting, // orthogonal shapes. // // You will see a series of polygon() calls, followed by a fill(). polygon(pts) { if (!compound) { compound = new Path2D; } let path = new Path2D(); path.moveTo(pts[0][0], pts[0][1]); for (let i = 1; i < pts.length; i++) { path.lineTo(pts[i][0], pts[i][1]); } path.closePath(); compound.addPath(path); }, // An unstroked, filled hexagon used only by maxicode. You can choose // to fill each individually, or wait for the final fill(). // // The hexagon is drawn from the top, counter-clockwise. hexagon(pts, rgb) { // A hexagon is just a polygon... bwip-js differentiates to allow the // built-in drawing to optimize. this.polygon(pts); }, // An unstroked filled ellipse. Used by dotcode and maxicode at present. // Maxicode plots pairs of ellipse() calls (one cw, one ccw) followed by a // fill() to create the bullseye rings. Dotcode plots all of its ellipses // followed by a single a fill(). ellipse(x, y, rx, ry, ccw) { if (!compound) { compound = new Path2D; } let path = new Path2D(); path.ellipse(x, y, rx, ry, 0, 0, 2*Math.PI, ccw); compound.addPath(path); }, // PostScript's default fill rule is non-zero. fill(rgb) { if (!compound) { return; } ctx.fillStyle = '#' + rgb; ctx.fill(compound, 'nonzero'); compound = undefined; }, // 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 clipping rule, like the fill rule, // defaults to non-zero winding. clip : function(polys) { ctx.save(); let region = new Path2D(); for (let j = 0; j < polys.length; j++) { let pts = polys[j]; let path = new Path2D(); path.moveTo(pts[0][0], pts[0][1]); for (let i = 1; i < pts.length; i++) { path.lineTo(pts[i][0], pts[i][1]); } path.closePath(); region.addPath(path); } ctx.clip(region, 'nonzero'); }, unclip : function() { ctx.restore(); }, // Draw text. // `y` is the baseline. // `font` is an object with properties { name, width, height, dx } // // `name` will be the same as the font name in `measure()`. // `width` and `height` are the font cell size. // `dx` is extra space requested between characters (usually zero). // // This code ignores the inter-character spacing to keep it simple. text(x, y, str, rgb, font) { let sx = font.width / font.height; ctx.save(); ctx.scale(sx, 1); ctx.font = font.height + 'px monospace'; ctx.fillStyle = '#' + rgb; ctx.textBaseline = 'alphabetic'; ctx.textAlign = 'left'; ctx.fillText(str, x / sx, y); ctx.restore(); }, // Called after all drawing is complete. The return value from this method // is the return value from `bwipjs.render()`. end() { // Nothing to do or return... }, }; }