UNPKG

bwip-js

Version:

JavaScript barcode generator supporting over 100 types and standards.

268 lines (250 loc) 10.4 kB
// bwip-js/examples/drawing-pdfkit.js // // Converts the drawing primitives into pdfkit graphics. Linear barcodes // are rendered as a series of stroked paths. 2D barcodes are rendered as a // series of filled paths. // // Rotation is handled during drawing. The resulting graphic will contain the // already-rotated barcode without need for a transform. // // If the requested barcode image contains text, the glyph paths are // extracted from the font file (via the builtin FontLib and stb_truetype.js) // and added as filled paths. // // This code can run in the browser and in nodejs. (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { root.DrawingPDFKit = factory(); } }(typeof self !== 'undefined' ? self : this, function () { "use strict"; // `doc` is an instance of PDFKit.PDFDocument function DrawingPDFKit(doc, opts, FontLib) { // 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 path; var lines = {}; // Magic number to approximate an ellipse/circle using 4 cubic beziers. var ELLIPSE_MAGIC = 0.55228475 - 0.00045; // Global graphics state var gs_width, gs_height; // image size, in pixels var gs_dx, gs_dy; // x,y translate (padding) return { // Make no adjustments scale(sx, sy) { }, // 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(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; i < str.length; i++) { var ch = str.charCodeAt(i); var glyph = FontLib.getpaths(fontid, ch, fwidth, fheight); if (!glyph) { continue; } ascent = Math.max(ascent, glyph.ascent); descent = Math.max(descent, -glyph.descent); width += glyph.advance; } return { width, ascent, descent }; }, // 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. 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; // 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; // Initialize defaults doc.save(); doc.lineCap('butt'); if (/^[0-9a-fA-F]{6}$/.test(''+opts.backgroundcolor)) { gs_dx = gs_dy = 0; moveTo(0, 0); lineTo(width, 0); lineTo(width, height); lineTo(0, height); lineTo(0, 0); doc.fillColor('#' + opts.backgroundcolor); doc.fill('non-zero'); } // Now add in the effects of the padding gs_dx = padl; gs_dy = padt; }, // 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, lw, rgb) { moveTo(x0, y0); lineTo(x1, y1); doc.lineWidth(lw) .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) { moveTo(pts[0][0], pts[0][1]); for (var i = 1, n = pts.length; i < n; i++) { lineTo(pts[i][0], pts[i][1]); } }, // 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. hexagon(pts, rgb) { this.polygon(pts); // A hexagon is just a polygon... }, // 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(x, y, rx, ry, ccw) { var dx = rx * ELLIPSE_MAGIC; var dy = ry * ELLIPSE_MAGIC; // Since we fill with even-odd, don't worry about cw/ccw moveTo(x - rx, y); cubicTo(x - rx, y - dy, x - dx, y - ry, x, y - ry); cubicTo(x + dx, y - ry, x + rx, y - dy, x + rx, y); cubicTo(x + rx, y + dy, x + dx, y + ry, x, y + ry); cubicTo(x - dx, y + ry, x - rx, y + dy, x - rx, y); }, // PostScript's default fill rule is non-zero but there are never intersecting // regions, so we use even-odd as it is easier to work with. fill(rgb) { doc.fillColor('#' + rgb); doc.fill('even-odd'); }, // 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 non-zero winding. clip : function(polys) { doc.save(); for (let j = 0; j < polys.length; j++) { let pts = polys[j]; moveTo(pts[0][0], pts[0][1]); for (let i = 1; i < pts.length; i++) { lineTo(pts[i][0], pts[i][1]); } } doc.clip('non-zero'); }, unclip : function() { doc.restore(); }, // 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(x, y, str, rgb, font) { var fontid = FontLib.lookup(font.name); var fwidth = font.width|0; var fheight = font.height|0; var dx = font.dx|0; var path = ''; for (var k = 0; k < str.length; k++) { var ch = str.charCodeAt(k); var glyph = FontLib.getpaths(fontid, ch, fwidth, fheight); if (!glyph) { continue; } if (glyph.length) { // A glyph is composed of sequence of curve and line segments. // M is move-to // L is line-to // Q is quadratic bezier curve-to // C is cubic bezier curve-to for (var i = 0, l = glyph.length; i < l; i++) { let seg = glyph[i]; if (seg.type == 'M') { moveTo(seg.x + x, y - seg.y); } else if (seg.type == 'L') { lineTo(seg.x + x, y - seg.y); } else if (seg.type == 'Q') { quadTo(seg.cx + x, y - seg.cy, seg.x + x, y - seg.y); } else if (seg.type == 'C') { cubicTo(seg.cx1 + x, y - seg.cy1, seg.cx2 + x, y - seg.cy2, seg.x + x, y - seg.y); } } } x += glyph.advance + dx; } doc.fillColor('#' + rgb); doc.fill('non-zero'); }, // Called after all drawing is complete. The return value from this method // is the return value from `bwipjs.render()`. end() { doc.restore(); return doc; }, }; // translate/rotate and return as a coordinate pair function transform(x, y) { x += gs_dx; y += gs_dy; var tx = tx0 * x + tx1 * y + tx2 * (gs_width-1) + tx3 * (gs_height-1); var ty = ty0 * x + ty1 * y + ty2 * (gs_width-1) + ty3 * (gs_height-1); return [ tx, ty ]; } function moveTo(x, y) { var p = transform(x, y); doc.moveTo(p[0], p[1]); } function lineTo(x, y) { var p = transform(x, y); doc.lineTo(p[0], p[1]); } function quadTo(cx, cy, x, y) { var p1 = transform(cx, cy); var p2 = transform(x, y); doc.quadraticCurveTo(p1[0], p1[1], p2[0], p2[1]); } function cubicTo(cx1, cy1, cx2, cy2, x, y) { var p1 = transform(cx1, cy1); var p2 = transform(cx2, cy2); var p3 = transform(x, y); doc.bezierCurveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]); } } return DrawingPDFKit; }));