├── LICENSE ├── LICENSE.CANVASTOBLOB ├── LICENSE.FILESAVER ├── LICENSE.INCONSOLATA ├── README.md ├── barcode.ps ├── bin └── bwip-js.js ├── demo.html ├── dist ├── bwip-js-gen.d.ts ├── bwip-js-gen.mjs ├── bwip-js-min.js ├── bwip-js-node.d.ts ├── bwip-js-node.js ├── bwip-js-node.mjs ├── bwip-js-rn.d.ts ├── bwip-js-rn.mjs ├── bwip-js.d.ts ├── bwip-js.js ├── bwip-js.mjs └── bwipp.mjs ├── examples ├── README.md ├── bwip-js.pdf ├── cluster.js ├── drawing-example.js ├── drawing-pdfkit.js ├── example.html ├── pdfkit.js ├── raw.js ├── server.js └── threaded.js ├── fonts ├── Inconsolata.otf ├── OCRA7.ttf └── OCRB7.ttf ├── lib ├── canvas-toblob.js ├── demo.css ├── filesaver.js ├── inconsolata.js └── symdesc.js ├── package.json ├── src ├── bwip-js.d.ts ├── bwipjs.js ├── bwipp.js ├── drawing-builtin.js ├── drawing-canvas.js ├── drawing-svg.js ├── drawing-zlibpng.js ├── exports.js ├── fontlib.js └── stb_truetype.js └── stb_truetype.h /LICENSE: -------------------------------------------------------------------------------- 1 | bwip-js : Barcode Writer in Pure JavaScript 2 | 3 | Copyright (c) 2011-2025 Mark Warren 4 | 5 | The MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /LICENSE.CANVASTOBLOB: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT/X11 license. 2 | 3 | MIT/X11 license 4 | --------------- 5 | 6 | Copyright © 2011 [Eli Grey][1] and [Devin Samarin][2]. 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the "Software"), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | 30 | [1]: http://eligrey.com 31 | [2]: https://github.com/dsamarin 32 | -------------------------------------------------------------------------------- /LICENSE.FILESAVER: -------------------------------------------------------------------------------- 1 | Copyright © 2015 [Eli Grey][1]. 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | 9 | [1]: http://eligrey.com 10 | -------------------------------------------------------------------------------- /LICENSE.INCONSOLATA: -------------------------------------------------------------------------------- 1 | Copyright (c) , (), 2 | with Reserved Font Name . 3 | Copyright (c) , (), 4 | with Reserved Font Name . 5 | Copyright (c) , (). 6 | 7 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 8 | This license is copied below, and is also available with a FAQ at: 9 | http://scripts.sil.org/OFL 10 | 11 | 12 | ----------------------------------------------------------- 13 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 14 | ----------------------------------------------------------- 15 | 16 | PREAMBLE 17 | The goals of the Open Font License (OFL) are to stimulate worldwide 18 | development of collaborative font projects, to support the font creation 19 | efforts of academic and linguistic communities, and to provide a free and 20 | open framework in which fonts may be shared and improved in partnership 21 | with others. 22 | 23 | The OFL allows the licensed fonts to be used, studied, modified and 24 | redistributed freely as long as they are not sold by themselves. The 25 | fonts, including any derivative works, can be bundled, embedded, 26 | redistributed and/or sold with any software provided that any reserved 27 | names are not used by derivative works. The fonts and derivatives, 28 | however, cannot be released under any other type of license. The 29 | requirement for fonts to remain under this license does not apply 30 | to any document created using the fonts or their derivatives. 31 | 32 | DEFINITIONS 33 | "Font Software" refers to the set of files released by the Copyright 34 | Holder(s) under this license and clearly marked as such. This may 35 | include source files, build scripts and documentation. 36 | 37 | "Reserved Font Name" refers to any names specified as such after the 38 | copyright statement(s). 39 | 40 | "Original Version" refers to the collection of Font Software components as 41 | distributed by the Copyright Holder(s). 42 | 43 | "Modified Version" refers to any derivative made by adding to, deleting, 44 | or substituting -- in part or in whole -- any of the components of the 45 | Original Version, by changing formats or by porting the Font Software to a 46 | new environment. 47 | 48 | "Author" refers to any designer, engineer, programmer, technical 49 | writer or other person who contributed to the Font Software. 50 | 51 | PERMISSION & CONDITIONS 52 | Permission is hereby granted, free of charge, to any person obtaining 53 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 54 | redistribute, and sell modified and unmodified copies of the Font 55 | Software, subject to the following conditions: 56 | 57 | 1) Neither the Font Software nor any of its individual components, 58 | in Original or Modified Versions, may be sold by itself. 59 | 60 | 2) Original or Modified Versions of the Font Software may be bundled, 61 | redistributed and/or sold with any software, provided that each copy 62 | contains the above copyright notice and this license. These can be 63 | included either as stand-alone text files, human-readable headers or 64 | in the appropriate machine-readable metadata fields within text or 65 | binary files as long as those fields can be easily viewed by the user. 66 | 67 | 3) No Modified Version of the Font Software may use the Reserved Font 68 | Name(s) unless explicit written permission is granted by the corresponding 69 | Copyright Holder. This restriction only applies to the primary font name as 70 | presented to the users. 71 | 72 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 73 | Software shall not be used to promote, endorse or advertise any 74 | Modified Version, except to acknowledge the contribution(s) of the 75 | Copyright Holder(s) and the Author(s) or with their explicit written 76 | permission. 77 | 78 | 5) The Font Software, modified or unmodified, in part or in whole, 79 | must be distributed entirely under this license, and must not be 80 | distributed under any other license. The requirement for fonts to 81 | remain under this license does not apply to any document created 82 | using the Font Software. 83 | 84 | TERMINATION 85 | This license becomes null and void if any of the above conditions are 86 | not met. 87 | 88 | DISCLAIMER 89 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 90 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 91 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 92 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 93 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 94 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 95 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 96 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 97 | OTHER DEALINGS IN THE FONT SOFTWARE. 98 | -------------------------------------------------------------------------------- /bin/bwip-js.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var bwipjs = require('..'); 6 | var symdesc = require('../lib/symdesc'); 7 | 8 | function usage(exit) { 9 | console.log( 10 | "Usage: bwip-js symbol-name text [options...] file.{png,svg}\n" + 11 | " bwip-js --bcid=symbol-name --text=text [options...] file.{png,svg}\n" + 12 | "\n" + 13 | "Examples:\n" + 14 | " bwip-js code128 012345678 includetext textcolor=ff0000 my-code128.png\n" + 15 | " bwip-js qrcode 'https://bwip-js.metafloor.com' qrcode.svg\n" + 16 | "\n" + 17 | "'bwip-js --help' for more information.\n" + 18 | "'bwip-js --symbols' for a list of supported barcode symbols.\n" + 19 | "'bwip-js --version' for version information.\n" + 20 | ""); 21 | if (exit) { 22 | process.exit(1); 23 | } 24 | } 25 | function help() { 26 | usage(); 27 | for (var i = 0; i < optlist.length; i++) { 28 | console.log(' --' + optlist[i].name) 29 | console.log(indent(optlist[i].desc)); 30 | } 31 | console.log( 32 | "\nThe double-dashes '--' are not required before each option.\n" + 33 | "For example, you can specify '--includetext' or 'includetext'."); 34 | } 35 | // Indent the text 8 spaces 36 | function indent(text) { 37 | return text.replace(/^/, ' ').replace(/\n/g, '\n '); 38 | } 39 | // spec is one of: "font-name,y-mult,x-mult,path-to-font-file" 40 | // "font-name,size-mult,path-to-font-file" 41 | // "font-name,path-to-font-file" 42 | function loadfont(spec) { 43 | var vals = spec.split(','); 44 | try { 45 | if (vals.length == 4) { 46 | bwipjs.loadFont(vals[0], +vals[1], +vals[2], fs.readFileSync(vals[3])); 47 | } else if (vals.length == 3) { 48 | bwipjs.loadFont(vals[0], +vals[1], fs.readFileSync(vals[2])); 49 | } else if (vals.length == 2) { 50 | bwipjs.loadFont(vals[0], fs.readFileSync(vals[1])); 51 | } else { 52 | console.log("Invalid --loadfont format."); 53 | console.log("Try 'bwip-js --help' for more information."); 54 | process.exit(1); 55 | } 56 | } catch(e) { 57 | console.log('bwip-js: ' + e); 58 | process.exit(1); 59 | } 60 | } 61 | 62 | var optlist = [ 63 | { name: 'help', type: 'boolean', 64 | desc: 'Displays this help message.' }, 65 | { name: 'version', type: 'boolean', 66 | desc: 'Displays the bwip-js and BWIPP version strings.' }, 67 | { name: 'symbols', type: 'boolean', 68 | desc: 'Display a list of the supported barcode types.' }, 69 | { name: 'loadfont', type: 'string', 70 | desc: 'Loads a truetype/opentype font. Format of this option is one of:\n\n' + 71 | ' --loadfont=font-name,y-mult,x-mult,path-to-font-file\n' + 72 | ' --loadfont=font-name,size-mult,path-to-font-file\n' + 73 | ' --loadfont=font-name,path-to-font-file\n\n' + 74 | 'For example: --loadfont=Courier,100,120,c:\\windows\\fonts\\cour.ttf' }, 75 | 76 | // bwipjs options 77 | { name: 'bcid', type: 'string', 78 | desc: 'Barcode symbol name/type. Required.' }, 79 | { name: 'text', type: 'string', 80 | desc: 'The text to encode. Required.\n' + 81 | 'Use single quotes around the text to protect from the shell.' }, 82 | { name: 'scaleX', type: 'int', 83 | desc: 'The x-axis scaling factor. Must be an integer > 0. Default is 2.' }, 84 | { name: 'scaleY', type: 'int', 85 | desc: 'The y-axis scaling factor. Must be an integer > 0. Default is scaleX.' }, 86 | { name: 'scale', type: 'int', 87 | desc: 'Sets both the x-axis and y-axis scaling factors. Must be an integer > 0.' }, 88 | { name: 'rotate', type: 'string', 89 | desc: 'Rotates the image to one of the four orthogonal orientations.\n' + 90 | 'A string value. Must be one of:\n' + 91 | ' N : Normal orientation.\n' + 92 | ' R : Rotated right 90 degrees.\n' + 93 | ' L : Rotated left 90 degrees.\n' + 94 | ' I : Inverted, rotated 180 degrees.' }, 95 | { name: 'padding', type: 'int', 96 | desc: 'Shorthand for setting paddingtop, paddingleft, paddingright, and paddingbottom.' }, 97 | { name: 'paddingwidth', type: 'int', 98 | desc: 'Shorthand for setting paddingleft and paddingright.' }, 99 | { name: 'paddingheight', type: 'int', 100 | desc: 'Shorthand for setting paddingtop and paddingbottom.' }, 101 | { name: 'paddingtop', type: 'int', 102 | desc: 'Sets the height of the padding area, in points, on the top of\n' + 103 | 'the barcode image. Rotates and scales with the image.' }, 104 | { name: 'paddingleft', type: 'int', 105 | desc: 'Sets the width of the padding area, in points, on the left side\n' + 106 | 'of the barcode image. Rotates and scales with the image.' }, 107 | { name: 'paddingright', type: 'int', 108 | desc: 'Sets the width of the padding area, in points, on the right side\n' + 109 | 'of the barcode image. Rotates and scales with the image.' }, 110 | { name: 'paddingbottom', type: 'int', 111 | desc: 'Sets the height of the padding area, in points, on the bottom of\n' + 112 | 'the barcode image. Rotates and scales with the image.' }, 113 | // { name: 'monochrome', type: 'boolean', 114 | // desc: 'Sets the human-readable text to render in monochrome.\n' 115 | // 'Default is false which renders 256-level gray-scale anti-aliased text.' }, 116 | 117 | // bwipp options 118 | { name: 'alttext', type: 'string', 119 | desc: 'The human-readable text to use instead of the encoded text.' }, 120 | { name: 'includecheck', type: 'boolean', 121 | desc: 'Generate check digit(s) for symbologies where the use of check digits is\n' + 122 | 'optional.' }, 123 | { name: 'includecheckintext', type: 'boolean', 124 | desc: 'Show the calculated check digit in the human readable text.' }, 125 | { name: 'parse', type: 'boolean', 126 | desc: 'In supporting barcode symbologies, when the parse option is specified,\n' + 127 | 'any instances of ^NNN in the data field are replaced with their equivalent\n' + 128 | 'ASCII value, useful for specifying unprintable characters.' }, 129 | { name: 'parsefnc', type: 'boolean', 130 | desc: 'In supporting barcode symbologies, when the parsefnc option is specified,\n' + 131 | 'non-data function characters can be specified by escaped combinations such\n' + 132 | 'as ^FNC1, ^FNC4 and ^SFT.' }, 133 | { name: 'height', type: 'float', 134 | desc: 'Height of longest bar, in millimeters.' }, 135 | { name: 'width', type: 'float', 136 | desc: 'Stretch the symbol to approximately this width, in millimeters.\n' + 137 | 'Not recommended unless scale is at least 5.' }, 138 | { name: 'inkspread', type: 'float', 139 | desc: 'Amount by which to reduce the bar widths to compensate for inkspread,\n' + 140 | 'in points. Not recommended unless scale is at least 5.' }, 141 | { name: 'inkspreadh', type: 'float', 142 | desc: 'For matrix barcodes, the amount by which to reduce the width of dark\n' + 143 | 'modules to compensate for inkspread, in points.\n' + 144 | 'Not recommended unless scale is at least 5.\n\n' + 145 | 'Note: inkspreadh is most useful for stacked-linear type barcodes such as\n' + 146 | 'PDF417 and Codablock F.' }, 147 | { name: 'inkspreadv', type: 'float', 148 | desc: 'For matrix barcodes, the amount by which to reduce the height of dark\n' + 149 | 'modules to compensate for inkspread, in points.\n' + 150 | 'Not recommended unless scale is at least 5.' }, 151 | { name: 'dotty', type: 'boolean', 152 | desc: 'For matrix barcodes, render the modules as dots rather than squares.\n' + 153 | 'The dot radius can be adjusted using the inkspread option.' }, 154 | { name: 'includetext', type: 'boolean', 155 | desc: 'Show human readable text for data in symbol.' }, 156 | { name: 'textfont', type: 'string', 157 | desc: 'The font name to use for the text.' }, 158 | { name: 'textsize', type: 'int', 159 | desc: 'The font size of the text, in points.' }, 160 | { name: 'textgaps', type: 'int', 161 | desc: 'The inter-character spacing of the text.' }, 162 | { name: 'textxalign', type: 'string', 163 | desc: 'Specifies where to horizontally position the text.' }, 164 | { name: 'textyalign', type: 'string', 165 | desc: 'Specifies where to vertically position the text.' }, 166 | { name: 'textxoffset', type: 'int', 167 | desc: 'The horizontal position of the text, in points, relative to the\n' + 168 | 'default position.' }, 169 | { name: 'textyoffset', type: 'int', 170 | desc: 'The vertical position of the text, in points, relative to the\n' + 171 | 'default position.' }, 172 | { name: 'showborder', type: 'boolean', 173 | desc: 'Display a border around the symbol.' }, 174 | { name: 'borderwidth', type: 'int', 175 | desc: 'Width of a border, in points.' }, 176 | { name: 'borderleft', type: 'int', 177 | desc: 'Left margin gap of the border, in points.' }, 178 | { name: 'borderright', type: 'int', 179 | desc: 'Right margin gap of the border, in points.' }, 180 | { name: 'bordertop', type: 'int', 181 | desc: 'Top margin gap of the border, in points.' }, 182 | { name: 'borderbottom', type: 'int', 183 | desc: 'Bottom margin gap of the border, in points.' }, 184 | { name: 'barcolor', type: 'string', 185 | desc: 'Color of the bars, either as a hex RGB or RRGGBB value or a hex CCMMYYKK value.' }, 186 | { name: 'backgroundcolor', type: 'string', 187 | desc: 'Color of the image background, either as a hex RGB or RRGGBB value or a\n' + 188 | 'hex CCMMYYKK value. The default is a transparent background.' }, 189 | { name: 'bordercolor', type: 'string', 190 | desc: 'Color of the border, either as a hex RGB or RRGGBB value or a hex CCMMYYKK value.\n' + 191 | 'You must specify --showborder for this setting to take effect.' }, 192 | { name: 'textcolor', type: 'string', 193 | desc: 'Color of the text, either as a hex RGB or RRGGBB value or a hex CCMMYYKK value.' }, 194 | /* 195 | { name: 'addontextfont', type: 'string', 196 | desc: 'The font name to use for the add-on text in ISBN, ISMN, and ISSN barcodes.' }, 197 | { name: 'addontextsize', type: 'int', 198 | desc: 'The font size of the add on text, in points.' }, 199 | { name: 'addontextxoffset', type: 'int', 200 | desc: 'Overrides the default positioning for the add on text.' }, 201 | { name: 'addontextyoffset', type: 'int', 202 | desc: 'Overrides the default positioning for the add on text.' }, 203 | */ 204 | { name: 'guardwhitespace', type: 'boolean', 205 | desc: 'Display white space guards.' }, 206 | { name: 'guardwidth', type: 'int', 207 | desc: 'Width of white space guards, in points.' }, 208 | { name: 'guardheight', type: 'int', 209 | desc: 'Height of white space guards, in points.' }, 210 | { name: 'guardleftpos', type: 'int', 211 | desc: 'Amount of white space to guard to left of the symbol, in points.' }, 212 | { name: 'guardrightpos', type: 'int', 213 | desc: 'Amount of white space to guard to right of the symbol, in points.' }, 214 | { name: 'guardleftypos', type: 'int', 215 | desc: 'Vertical position of the guard symbols on the left, in points.' }, 216 | { name: 'guardrightypos', type: 'int', 217 | desc: 'Vertical position of the guard symbols on the right, in points.' }, 218 | ]; 219 | var optmap = optlist.reduce(function(map, elt) { map[elt.name] = elt; return map; }, {}); 220 | var opts = {}; 221 | var argv = process.argv; 222 | var outfile; 223 | 224 | if (argv.length == 2) { 225 | usage(true); 226 | } 227 | if (argv.length > 2 && !/^(--|bcid=)/.test(argv[2])) { 228 | argv[2] = 'bcid=' + argv[2]; 229 | } 230 | if (argv.length > 3 && !/^(--|text=)/.test(argv[3])) { 231 | argv[3] = 'text=' + argv[3]; 232 | } 233 | if (argv.length > 4 && !/^(--|\w+=)/.test(argv[argv.length-1])) { 234 | outfile = argv.pop(); 235 | if (!/\.(png|svg)$/.test(outfile)) { 236 | console.log(outfile + ": last argument must be a png or svg file name"); 237 | usage(true); 238 | } 239 | } 240 | for (var i = 2, l = argv.length; i < l; i++) { 241 | var a = argv[i]; 242 | if (/^--/.test(a)) { 243 | a = a.substr(2); 244 | } 245 | var eq = a.indexOf('='); 246 | if (eq > 0) { 247 | var name = a.substr(0, eq); 248 | var val = a.substr(eq+1); 249 | if (name == 'loadfont') { 250 | loadfont(val); 251 | } else { 252 | opts[name] = val; 253 | } 254 | } else if (optmap[a]) { 255 | if (optmap[a].type != 'boolean') { 256 | console.log('--' + a + ' : A value is expected.'); 257 | help(); 258 | process.exit(1); 259 | } 260 | opts[a] = true; 261 | } else { 262 | opts[a] = true; 263 | } 264 | } 265 | 266 | if (opts.help) { 267 | help(); 268 | } else if (opts.symbols) { 269 | var arr = []; 270 | for (var sym in symdesc) { 271 | arr.push(sym + ' : ' + symdesc[sym].desc); 272 | } 273 | arr.sort(); 274 | for (var i = 0; i < arr.length; i++) { 275 | console.log(' ' + arr[i]); 276 | } 277 | } else if (opts.version && !opts.bcid) { 278 | console.log('bwip-js: ' + bwipjs.BWIPJS_VERSION + '\nBWIPP: ' + bwipjs.BWIPP_VERSION); 279 | } else { 280 | if (!opts.bcid) { 281 | console.log('bwip-js: missing bcid'); 282 | usage(true); 283 | } else if (!opts.text) { 284 | console.log('bwip-js: missing text'); 285 | usage(true); 286 | } else if (!outfile) { 287 | console.log('bwip-js: missing outfile'); 288 | usage(true); 289 | } else if (outfile.slice(-3) == 'png') { 290 | bwipjs.toBuffer(opts, function (err, png) { 291 | if (err) { 292 | console.error(''+err); 293 | process.exit(1); 294 | } 295 | try { 296 | fs.writeFileSync(outfile, png); 297 | } catch (e) { 298 | console.log('bwip-js: ' + e); 299 | } 300 | }); 301 | } else { 302 | fs.writeFileSync(outfile, bwipjs.toSVG(opts)); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | bwip-js - JavaScript Barcode Generator 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 270 | 271 | 275 |
276 | 277 |
278 | 279 |
Barcode Type: 280 |
Bar Text: 281 |
Alt Text: 282 |
Options: 283 |
284 |
285 |
286 | Save As PNG 287 |    288 | Save As JPEG 289 |    290 | Goto URL 291 |    292 |
293 |
294 |
295 | 296 |
Render As: 297 | 298 | 299 |
Scale X,Y: 300 | 301 | 302 |
Image Rotation: 303 | 305 | 307 | 309 | 311 |
312 |
313 |

314 |
315 |
316 |
317 | 318 | 319 |
320 |
321 | 322 | 323 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Server Examples 3 | 4 | Generating barcodes can be CPU intensive. 2D barcodes typically require 100ms - 250ms 5 | on a fairly modern CPU. Best to offload to secondary processes or threads, otherwise the 6 | nodejs event loop will be blocked. 7 | 8 | - `server.js` is the most basic server example. A single-threaded, single-process 9 | server. 10 | 11 | - `cluster.js` creates a bwip-js specific process per CPU, plus a master to 12 | automatically relaunch killed processes. 13 | 14 | - `threaded.js` is a single-process, multi-threaded server. Requires node.js 10.5 15 | or higher. 16 | 17 | 18 | ## Drawing Examples 19 | 20 | - `drawing-example.js` and `example.html` 21 | 22 | These files are the simplest demonstation of the bwip-js drawing interface. 23 | Do not use in production as the canvas API will create fuzzy lines/edges. 24 | 25 | See `example.html` for the following code showing how to create the drawing 26 | object and pass it to bwip-js: 27 | 28 | ``` 29 | // Draw the bar code to the canvas using a custom drawing interface. 30 | try { 31 | // fixupOptions() modifies options values (currently padding and 32 | // background color) to provide a simplified interface for the 33 | // drawing code. 34 | bwipjs.fixupOptions(opts); 35 | bwipjs.render(opts, DrawingExample(opts, canvas)); 36 | } catch (e) { 37 | // e can be a string or Error object 38 | } 39 | ``` 40 | 41 | You can launch `example.html` from your browser using the `file://` scheme. 42 | 43 | - `examples/pdfkit.js` and `examples/drawing-pdfkit.js` show how to add 44 | scalable barcodes (PDF graphics) to a [pdfkit](https://pdfkit.org/) document. 45 | 46 | `drawing-pdfkit.js` is browser compatible if you prefer to use pdfkit 47 | client side. 48 | 49 | 50 | ## Example Raw BWIPP Encoder/Rendering Data 51 | 52 | - `raw.js` is a utility for displaying the low-level BWIPP data used to render a barcode. 53 | 54 | See [Notes on the Raw BWIPP Data](https://github.com/metafloor/bwip-js/wiki/Notes-on-the-Raw-BWIPP-Data) 55 | for more details. 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/bwip-js.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metafloor/bwip-js/33e5198e66a96639ef1a47c95ed2e363b2b79852/examples/bwip-js.pdf -------------------------------------------------------------------------------- /examples/cluster.js: -------------------------------------------------------------------------------- 1 | // bwip-js/examples/cluster.js 2 | // 3 | // Simple HTTP server that renders bar code images using bwip-js. 4 | // 5 | // Usage: node cluster [address:port] ... 6 | // 7 | // To specify all interfaces, use * as the address 8 | // 9 | // If no address:port are specified, the default is: *:3030 10 | // 11 | "use strict"; 12 | 13 | var cluster = require('cluster'); 14 | var http = require('http'); 15 | var numCPUs = require('os').cpus().length; 16 | var bwipjs = (function() { 17 | try { 18 | return require('bwip-js'); // for installed usage 19 | } catch (e) { 20 | return require('..'); // for development use only 21 | } 22 | })(); 23 | 24 | if (cluster.isMaster) { 25 | // Use a de-escalation scheme to prevent forking too quickly. 26 | var nworkers = 0; 27 | var nwaiting = 0; 28 | 29 | // Fork the initial workers. 30 | for (var i = 0; i < numCPUs; i++) { 31 | cluster.fork(); 32 | nworkers++; 33 | } 34 | 35 | cluster.on('exit', function(worker, code, signal) { 36 | console.log('worker ' + worker.process.pid + ' died'); 37 | nworkers--; 38 | nwaiting++; 39 | setTimeout(function() { 40 | cluster.fork(); 41 | nwaiting--; 42 | nworkers++; 43 | }, 1000 * nwaiting); 44 | }); 45 | } else { 46 | // All the workers will share the HTTP server 47 | var server = http.createServer(function (req, res) { 48 | // If the url does not begin /?bcid= then 404. Otherwise, we end up 49 | // returning 400 on requests like favicon.ico. 50 | if (req.url.indexOf('/?bcid=') != 0) { 51 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 52 | res.end('BWIP-JS: Unknown request format.', 'utf8'); 53 | } else { 54 | bwipjs.request(req, res); 55 | } 56 | }); 57 | 58 | var binds = 0; 59 | for (let i = 2; i < process.argv.length; i++) { 60 | let a = /^([^:]+):(\d+)$/.exec(process.argv[i]); 61 | if (a) { 62 | if (a[1] == '*') { 63 | server.listen(+a[2]); 64 | } else { 65 | server.listen(+a[2], a[1]); 66 | } 67 | } else { 68 | console.log(process.argv[i] + ': option ignored...'); 69 | } 70 | binds++; 71 | } 72 | if (!binds) { 73 | server.listen(process.env.PORT || 3030); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/drawing-example.js: -------------------------------------------------------------------------------- 1 | // bwip-js/examples/drawing-example.js 2 | // 3 | // Example use of the drawing interface. This code is for expository purposes only. 4 | // Using the HTML5 canvas API creates "fuzzy" barcodes which are anathema 5 | // to reliable scanning. 6 | // 7 | // For the methods that take a color `rgb` parameter, the value is always a 8 | // string with format RRGGBB. Internally, BWIPP accepts both RGB and CMYK values 9 | // but bwip-js always converts CMYK to RGB before the drawing code sees it. 10 | 11 | // The signature of this factory constructor is up to you. It is your code that 12 | // calls it and passes the returned drawing instance to `bwipjs.render()`. 13 | // See example.html. 14 | function DrawingExample(opts, canvas) { 15 | 16 | let ctx = canvas.getContext('2d', { willReadFrequently:true }); 17 | 18 | // PostScript transparently creates compound path regions. 19 | // We must do it explicitly with canvas. 20 | let compound = null; 21 | 22 | return { 23 | // setopts() is called after the options are fixed-up/normalized, 24 | // but before calling into BWIPP. 25 | // 26 | // This method allows omitting the options object in the constructor call, 27 | // which simplifies the pattern: 28 | // 29 | // bwipjs.render({ bcid:'code128', ... }, myDrawing()); 30 | // 31 | // In the above, it is awkward to pass the options object to the drawing 32 | // constructor. 33 | // 34 | // The method is optional. Implemented in v4.0. 35 | setopts(options) { 36 | opts = options; 37 | }, 38 | // Adjust scale. The return value is a two-element array with the 39 | // scale-x and scale-y values adjusted as desired. 40 | // 41 | // For this example, we want fractions of pixels, so do nothing. 42 | // The builtin drawing returns [ floor(sx), floor(sy) ] to ensure all 43 | // bars and spaces are sized uniformly. 44 | // 45 | // Composite symbols cause this method to be called multiple times; be 46 | // consistent if you adjust the values. 47 | scale(sx, sy) { 48 | return null; 49 | }, 50 | // Measure text. measure() and scale() are the only drawing primitives 51 | // called before init(). 52 | // 53 | // `font` is the font name, typically Helvetica, Courier or OCR-A. 54 | // The bwip-js builtin drawing maps both Helvetica and Courier to OCR-B. 55 | // 56 | // The build tools pre-process the PostScript and change the text above 57 | // the ISBN, ISMN, ISSN barcodes to default to OCR-A. 58 | // 59 | // The user can explicitly change the font name via the BWIPP text options. 60 | // 61 | // `width` and `height` are the requested font cell size. They will 62 | // usually be the same, except when the x/y scaling is not symmetric. 63 | measure(str, font, width, height) { 64 | // The canvas measure api is basically useless, especially when dealing 65 | // with asymmetric scaling. The best we can hope for is a rough 66 | // approximation... 67 | ctx.font = height + 'px monospace'; 68 | let bbox = ctx.measureText(str); 69 | 70 | // The return is an object with properties { width, ascent, descent }. 71 | // All values in pixels. 72 | let descent = /[Qgjpqy]/.test(str) ? 0.25 * height : 0; 73 | return { width:bbox.width * width / height, ascent:0.65 * height, descent:descent }; 74 | }, 75 | // Initialize the drawing surface. 76 | // `width` and `height` represent the maximum bounding box the graphics will occupy. 77 | // The dimensions are for an unrotated rendering. Adjust as necessary. 78 | init(width, height) { 79 | // Add in the effects of padding 80 | let padl = opts.paddingleft; 81 | let padr = opts.paddingright; 82 | let padt = opts.paddingtop; 83 | let padb = opts.paddingbottom; 84 | 85 | width += padl + padr; 86 | height += padt + padb; 87 | 88 | // Set up the transform. The values in the arrays are: 89 | // [0] : swap width/height dimensions flag 90 | // [1],[2] : dx,dy translate needed with padding-left and padding-top 91 | // [3] : rotation (multiple of PI) 92 | let tx = { R:[ 1, height-padt, padl, 0.5 ], 93 | L:[ 1, padt, width-padl, 1.5 ], 94 | I:[ 0, width-padl, height-padt, 1 ] }[opts.rotate] || 95 | [ 0, padl, padt, 0 ]; 96 | 97 | canvas.width = tx[0] ? height : width; 98 | canvas.height = tx[0] ? width : height; 99 | ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset to unity transform 100 | 101 | // Set background before the transform makes this tricky. 102 | if (/^[0-9a-fA-F]{6}$/.test(''+opts.backgroundcolor)) { 103 | ctx.fillStyle = '#' + opts.backgroundcolor; 104 | ctx.fillRect(0, 0, canvas.width, canvas.height); 105 | } else { 106 | ctx.clearRect(0, 0, canvas.width, canvas.height); 107 | } 108 | 109 | // Apply the transform 110 | ctx.translate(tx[1], tx[2]); 111 | ctx.rotate(tx[3] * Math.PI); 112 | }, 113 | // Unconnected stroked lines are used to draw the bars in linear barcodes. 114 | // No line cap should be applied. These lines are always orthogonal. 115 | line(x0, y0, x1, y1, linew, rgb) { 116 | ctx.beginPath(); 117 | ctx.moveTo(x0, y0); 118 | ctx.lineTo(x1, y1); 119 | ctx.lineWidth = linew; 120 | ctx.lineCap = 'butt'; 121 | ctx.strokeStyle = '#' + rgb; 122 | ctx.stroke(); 123 | }, 124 | // Polygons are used to draw the connected regions in a 2d barcode. 125 | // These will always be unstroked, filled, non-intersecting, 126 | // orthogonal shapes. 127 | // 128 | // You will see a series of polygon() calls, followed by a fill(). 129 | polygon(pts) { 130 | if (!compound) { 131 | compound = new Path2D; 132 | } 133 | let path = new Path2D(); 134 | path.moveTo(pts[0][0], pts[0][1]); 135 | for (let i = 1; i < pts.length; i++) { 136 | path.lineTo(pts[i][0], pts[i][1]); 137 | } 138 | path.closePath(); 139 | compound.addPath(path); 140 | }, 141 | // An unstroked, filled hexagon used only by maxicode. You can choose 142 | // to fill each individually, or wait for the final fill(). 143 | // 144 | // The hexagon is drawn from the top, counter-clockwise. 145 | hexagon(pts, rgb) { 146 | // A hexagon is just a polygon... bwip-js differentiates to allow the 147 | // built-in drawing to optimize. 148 | this.polygon(pts); 149 | }, 150 | // An unstroked filled ellipse. Used by dotcode and maxicode at present. 151 | // Maxicode plots pairs of ellipse() calls (one cw, one ccw) followed by a 152 | // fill() to create the bullseye rings. Dotcode plots all of its ellipses 153 | // followed by a single a fill(). 154 | ellipse(x, y, rx, ry, ccw) { 155 | if (!compound) { 156 | compound = new Path2D; 157 | } 158 | let path = new Path2D(); 159 | path.ellipse(x, y, rx, ry, 0, 0, 2*Math.PI, ccw); 160 | compound.addPath(path); 161 | }, 162 | // PostScript's default fill rule is non-zero. 163 | fill(rgb) { 164 | if (!compound) { 165 | return; 166 | } 167 | ctx.fillStyle = '#' + rgb; 168 | ctx.fill(compound, 'nonzero'); 169 | compound = undefined; 170 | }, 171 | // Currently only used by swissqrcode. The `polys` area is an array of 172 | // arrays of points. Each array of points is identical to the `pts` 173 | // parameter passed to polygon(). The clipping rule, like the fill rule, 174 | // defaults to non-zero winding. 175 | clip : function(polys) { 176 | ctx.save(); 177 | 178 | let region = new Path2D(); 179 | for (let j = 0; j < polys.length; j++) { 180 | let pts = polys[j]; 181 | let path = new Path2D(); 182 | path.moveTo(pts[0][0], pts[0][1]); 183 | for (let i = 1; i < pts.length; i++) { 184 | path.lineTo(pts[i][0], pts[i][1]); 185 | } 186 | path.closePath(); 187 | region.addPath(path); 188 | } 189 | ctx.clip(region, 'nonzero'); 190 | }, 191 | unclip : function() { 192 | ctx.restore(); 193 | }, 194 | // Draw text. 195 | // `y` is the baseline. 196 | // `font` is an object with properties { name, width, height, dx } 197 | // 198 | // `name` will be the same as the font name in `measure()`. 199 | // `width` and `height` are the font cell size. 200 | // `dx` is extra space requested between characters (usually zero). 201 | // 202 | // This code ignores the inter-character spacing to keep it simple. 203 | text(x, y, str, rgb, font) { 204 | let sx = font.width / font.height; 205 | ctx.save(); 206 | ctx.scale(sx, 1); 207 | ctx.font = font.height + 'px monospace'; 208 | ctx.fillStyle = '#' + rgb; 209 | ctx.textBaseline = 'alphabetic'; 210 | ctx.textAlign = 'left'; 211 | ctx.fillText(str, x / sx, y); 212 | ctx.restore(); 213 | }, 214 | // Called after all drawing is complete. The return value from this method 215 | // is the return value from `bwipjs.render()`. 216 | end() { 217 | // Nothing to do or return... 218 | }, 219 | }; 220 | } 221 | -------------------------------------------------------------------------------- /examples/drawing-pdfkit.js: -------------------------------------------------------------------------------- 1 | // bwip-js/examples/drawing-pdfkit.js 2 | // 3 | // Converts the drawing primitives into pdfkit graphics. Linear barcodes 4 | // are rendered as a series of stroked paths. 2D barcodes are rendered as a 5 | // series of filled paths. 6 | // 7 | // Rotation is handled during drawing. The resulting graphic will contain the 8 | // already-rotated barcode without need for a transform. 9 | // 10 | // If the requested barcode image contains text, the glyph paths are 11 | // extracted from the font file (via the builtin FontLib and stb_truetype.js) 12 | // and added as filled paths. 13 | // 14 | // This code can run in the browser and in nodejs. 15 | (function (root, factory) { 16 | if (typeof define === 'function' && define.amd) { 17 | define([], factory); 18 | } else if (typeof module === 'object' && module.exports) { 19 | module.exports = factory(); 20 | } else { 21 | root.DrawingPDFKit = factory(); 22 | } 23 | }(typeof self !== 'undefined' ? self : this, function () { 24 | "use strict"; 25 | 26 | // `doc` is an instance of PDFKit.PDFDocument 27 | function DrawingPDFKit(doc, opts, FontLib) { 28 | // Unrolled x,y rotate/translate matrix 29 | var tx0 = 0, tx1 = 0, tx2 = 0, tx3 = 0; 30 | var ty0 = 0, ty1 = 0, ty2 = 0, ty3 = 0; 31 | 32 | var path; 33 | var lines = {}; 34 | 35 | // Magic number to approximate an ellipse/circle using 4 cubic beziers. 36 | var ELLIPSE_MAGIC = 0.55228475 - 0.00045; 37 | 38 | // Global graphics state 39 | var gs_width, gs_height; // image size, in pixels 40 | var gs_dx, gs_dy; // x,y translate (padding) 41 | 42 | return { 43 | // Make no adjustments 44 | scale(sx, sy) { 45 | }, 46 | // Measure text. This and scale() are the only drawing primitives that 47 | // are called before init(). 48 | // 49 | // `font` is the font name typically OCR-A or OCR-B. 50 | // `fwidth` and `fheight` are the requested font cell size. They will 51 | // usually be the same, except when the scaling is not symetric. 52 | measure(str, font, fwidth, fheight) { 53 | fwidth = fwidth|0; 54 | fheight = fheight|0; 55 | 56 | var fontid = FontLib.lookup(font); 57 | var width = 0; 58 | var ascent = 0; 59 | var descent = 0; 60 | for (var i = 0; i < str.length; i++) { 61 | var ch = str.charCodeAt(i); 62 | var glyph = FontLib.getpaths(fontid, ch, fwidth, fheight); 63 | if (!glyph) { 64 | continue; 65 | } 66 | ascent = Math.max(ascent, glyph.ascent); 67 | descent = Math.max(descent, -glyph.descent); 68 | width += glyph.advance; 69 | } 70 | return { width, ascent, descent }; 71 | }, 72 | 73 | // width and height represent the maximum bounding box the graphics will occupy. 74 | // The dimensions are for an unrotated rendering. Adjust as necessary. 75 | init(width, height) { 76 | // Add in the effects of padding. These are always set before the 77 | // drawing constructor is called. 78 | var padl = opts.paddingleft; 79 | var padr = opts.paddingright; 80 | var padt = opts.paddingtop; 81 | var padb = opts.paddingbottom; 82 | var rot = opts.rotate || 'N'; 83 | 84 | width += padl + padr; 85 | height += padt + padb; 86 | 87 | // Transform indexes are: x, y, w, h 88 | switch (rot) { 89 | // tx = w-y, ty = x 90 | case 'R': tx1 = -1; tx2 = 1; ty0 = 1; break; 91 | // tx = w-x, ty = h-y 92 | case 'I': tx0 = -1; tx2 = 1; ty1 = -1; ty3 = 1; break; 93 | // tx = y, ty = h-x 94 | case 'L': tx1 = 1; ty0 = -1; ty3 = 1; break; 95 | // tx = x, ty = y 96 | default: tx0 = ty1 = 1; break; 97 | } 98 | 99 | // Setup the graphics state 100 | var swap = rot == 'L' || rot == 'R'; 101 | gs_width = swap ? height : width; 102 | gs_height = swap ? width : height; 103 | 104 | // Initialize defaults 105 | doc.save(); 106 | doc.lineCap('butt'); 107 | 108 | if (/^[0-9a-fA-F]{6}$/.test(''+opts.backgroundcolor)) { 109 | gs_dx = gs_dy = 0; 110 | moveTo(0, 0); 111 | lineTo(width, 0); 112 | lineTo(width, height); 113 | lineTo(0, height); 114 | lineTo(0, 0); 115 | doc.fillColor('#' + opts.backgroundcolor); 116 | doc.fill('non-zero'); 117 | } 118 | 119 | // Now add in the effects of the padding 120 | gs_dx = padl; 121 | gs_dy = padt; 122 | }, 123 | // Unconnected stroked lines are used to draw the bars in linear barcodes. 124 | // No line cap should be applied. These lines are always orthogonal. 125 | line(x0, y0, x1, y1, lw, rgb) { 126 | moveTo(x0, y0); 127 | lineTo(x1, y1); 128 | 129 | doc.lineWidth(lw) .stroke(); 130 | }, 131 | // Polygons are used to draw the connected regions in a 2d barcode. 132 | // These will always be unstroked, filled, non-intersecting, 133 | // orthogonal shapes. 134 | // You will see a series of polygon() calls, followed by a fill(). 135 | polygon(pts) { 136 | moveTo(pts[0][0], pts[0][1]); 137 | for (var i = 1, n = pts.length; i < n; i++) { 138 | lineTo(pts[i][0], pts[i][1]); 139 | } 140 | }, 141 | // An unstroked, filled hexagon used by maxicode. You can choose to fill 142 | // each individually, or wait for the final fill(). 143 | // 144 | // The hexagon is drawn from the top, counter-clockwise. 145 | hexagon(pts, rgb) { 146 | this.polygon(pts); // A hexagon is just a polygon... 147 | }, 148 | // An unstroked, filled ellipse. Used by dotcode and maxicode at present. 149 | // maxicode issues pairs of ellipse calls (one cw, one ccw) followed by a fill() 150 | // to create the bullseye rings. dotcode issues all of its ellipses then a 151 | // fill(). 152 | ellipse(x, y, rx, ry, ccw) { 153 | var dx = rx * ELLIPSE_MAGIC; 154 | var dy = ry * ELLIPSE_MAGIC; 155 | 156 | // Since we fill with even-odd, don't worry about cw/ccw 157 | moveTo(x - rx, y); 158 | cubicTo(x - rx, y - dy, x - dx, y - ry, x, y - ry); 159 | cubicTo(x + dx, y - ry, x + rx, y - dy, x + rx, y); 160 | cubicTo(x + rx, y + dy, x + dx, y + ry, x, y + ry); 161 | cubicTo(x - dx, y + ry, x - rx, y + dy, x - rx, y); 162 | }, 163 | // PostScript's default fill rule is non-zero but there are never intersecting 164 | // regions, so we use even-odd as it is easier to work with. 165 | fill(rgb) { 166 | doc.fillColor('#' + rgb); 167 | doc.fill('even-odd'); 168 | }, 169 | // Currently only used by swissqrcode. The `polys` area is an array of 170 | // arrays of points. Each array of points is identical to the `pts` 171 | // parameter passed to polygon(). The postscript default clipping rule, 172 | // like the fill rule, is non-zero winding. 173 | clip : function(polys) { 174 | doc.save(); 175 | for (let j = 0; j < polys.length; j++) { 176 | let pts = polys[j]; 177 | moveTo(pts[0][0], pts[0][1]); 178 | for (let i = 1; i < pts.length; i++) { 179 | lineTo(pts[i][0], pts[i][1]); 180 | } 181 | } 182 | doc.clip('non-zero'); 183 | }, 184 | unclip : function() { 185 | doc.restore(); 186 | }, 187 | // Draw text with optional inter-character spacing. `y` is the baseline. 188 | // font is an object with properties { name, width, height, dx } 189 | // width and height are the font cell size. 190 | // dx is extra space requested between characters (usually zero). 191 | text(x, y, str, rgb, font) { 192 | var fontid = FontLib.lookup(font.name); 193 | var fwidth = font.width|0; 194 | var fheight = font.height|0; 195 | var dx = font.dx|0; 196 | var path = ''; 197 | for (var k = 0; k < str.length; k++) { 198 | var ch = str.charCodeAt(k); 199 | var glyph = FontLib.getpaths(fontid, ch, fwidth, fheight); 200 | if (!glyph) { 201 | continue; 202 | } 203 | if (glyph.length) { 204 | // A glyph is composed of sequence of curve and line segments. 205 | // M is move-to 206 | // L is line-to 207 | // Q is quadratic bezier curve-to 208 | // C is cubic bezier curve-to 209 | for (var i = 0, l = glyph.length; i < l; i++) { 210 | let seg = glyph[i]; 211 | if (seg.type == 'M') { 212 | moveTo(seg.x + x, y - seg.y); 213 | } else if (seg.type == 'L') { 214 | lineTo(seg.x + x, y - seg.y); 215 | } else if (seg.type == 'Q') { 216 | quadTo(seg.cx + x, y - seg.cy, seg.x + x, y - seg.y); 217 | } else if (seg.type == 'C') { 218 | cubicTo(seg.cx1 + x, y - seg.cy1, 219 | seg.cx2 + x, y - seg.cy2, 220 | seg.x + x, y - seg.y); 221 | } 222 | } 223 | } 224 | x += glyph.advance + dx; 225 | } 226 | doc.fillColor('#' + rgb); 227 | doc.fill('non-zero'); 228 | }, 229 | // Called after all drawing is complete. The return value from this method 230 | // is the return value from `bwipjs.render()`. 231 | end() { 232 | doc.restore(); 233 | return doc; 234 | }, 235 | }; 236 | 237 | // translate/rotate and return as a coordinate pair 238 | function transform(x, y) { 239 | x += gs_dx; 240 | y += gs_dy; 241 | var tx = tx0 * x + tx1 * y + tx2 * (gs_width-1) + tx3 * (gs_height-1); 242 | var ty = ty0 * x + ty1 * y + ty2 * (gs_width-1) + ty3 * (gs_height-1); 243 | return [ tx, ty ]; 244 | } 245 | function moveTo(x, y) { 246 | var p = transform(x, y); 247 | doc.moveTo(p[0], p[1]); 248 | } 249 | function lineTo(x, y) { 250 | var p = transform(x, y); 251 | doc.lineTo(p[0], p[1]); 252 | } 253 | function quadTo(cx, cy, x, y) { 254 | var p1 = transform(cx, cy); 255 | var p2 = transform(x, y); 256 | doc.quadraticCurveTo(p1[0], p1[1], p2[0], p2[1]); 257 | } 258 | function cubicTo(cx1, cy1, cx2, cy2, x, y) { 259 | var p1 = transform(cx1, cy1); 260 | var p2 = transform(cx2, cy2); 261 | var p3 = transform(x, y); 262 | doc.bezierCurveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]); 263 | } 264 | } 265 | 266 | return DrawingPDFKit; 267 | })); 268 | -------------------------------------------------------------------------------- /examples/example.html: -------------------------------------------------------------------------------- 1 | 2 | bwip-js - JavaScript Barcode Generator 3 | 4 | 5 | 6 | 7 | 8 | 178 | 179 | 183 |
184 | 185 |
186 | 187 |
Barcode Type: 188 |
Bar Text: 189 |
Alt Text: 190 |
Options: 191 |
192 |
193 | 194 |
Scale X,Y: 195 | 196 | 197 |
Image Rotation: 198 | 200 | 202 | 204 | 206 |
207 |
208 |

209 |
210 |
211 |
212 | 213 |
214 |
215 | 216 | 217 | -------------------------------------------------------------------------------- /examples/pdfkit.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs'); 3 | const PDFDocument = require('pdfkit'); 4 | const doc = new PDFDocument; 5 | 6 | const bwipjs = require('..'); 7 | const drawing = require('./drawing-pdfkit'); 8 | 9 | doc.pipe(fs.createWriteStream('bwip-js.pdf')); 10 | 11 | at(100, 100, { 12 | bcid: 'ean13composite', 13 | text: '2112345678900|(99)1234-abcd', 14 | includetext: true, 15 | padding: 8, 16 | backgroundcolor: 'dddddd', 17 | rotate: 'L', 18 | }); 19 | at(300, 100, { 20 | bcid: 'qrcode', 21 | text: 'bwip-js.metafloor.com', 22 | }); 23 | at(100, 250, { 24 | bcid: 'postnet', 25 | text: '123451234', 26 | includetext: true, 27 | rotate: 'R', 28 | }); 29 | // Use a different scale, just because... 30 | at(250, 200, { 31 | bcid: 'azteccode', 32 | text: 'bwip-js.metafloor.com', 33 | scale: 3, 34 | }); 35 | 36 | // Maxicode requires a custom scale at the pdfdoc level 37 | at(200, 300, { 38 | bcid: 'maxicode', 39 | text: '[)>^03001^02996152382802^029840^029001^0291Z00004951^029UPSN^02906X610^029159^0291234567^0291/1^029^029Y^029634 ALPHA DR^029PITTSBURGH^029PA^029^004', 40 | parse: true, 41 | mode:2, 42 | scale:2, // matched set with the 0.36 below (72dpi / 200dpi) 43 | }, 0.36); 44 | 45 | at(350, 300, { 46 | bcid: 'swissqrcode', 47 | text: 'SPC^CR^LF0200^CR^LF1^CR^LFCH5800791123000889012^CR^LFS^CR^LFRobert Schneider AG^CR^LFRue du Lac^CR^LF1268^CR^LF2501^CR^LFBiel^CR^LFCH^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF199.95^CR^LFCHF^CR^LFK^CR^LFPia-Maria Rutschmann-Schnyder^CR^LFGrosse Marktgasse 28^CR^LF9400 Rorschach^CR^LF^CR^LF^CR^LFCH^CR^LFSCOR^CR^LFRF18539007547034^CR^LF^CR^LFEPD', 48 | parse: true, 49 | scale:2, 50 | }); 51 | 52 | 53 | doc.end(); 54 | console.log('saved as bwip-js.pdf'); 55 | 56 | // We render at bwipjs.scale == 2 to get good detail on the drawing, 57 | // then scale back down. pdfkit, BWIPP and bwip-js all use the 58 | // same scale factor of 72pt/inch. 59 | function at(x, y, opts, scale) { 60 | doc.save(); 61 | doc.translate(x, y); 62 | doc.scale(scale || .5); 63 | 64 | bwipjs.fixupOptions(opts); 65 | bwipjs.render(opts, drawing(doc, opts, bwipjs.FontLib)); 66 | 67 | doc.restore(); 68 | } 69 | -------------------------------------------------------------------------------- /examples/raw.js: -------------------------------------------------------------------------------- 1 | // bwip-js/examples/raw.js 2 | // 3 | // For a detailed explanation of this utility, see: 4 | // https://github.com/metafloor/bwip-js/wiki/Notes-on-the-Raw-BWIPP-Data 5 | // 6 | const bwipjs = (function() { 7 | try { 8 | return require('bwip-js'); // for installed usage 9 | } catch (e) { 10 | return require('..'); // for development use only 11 | } 12 | })(); 13 | 14 | if (process.argv.length < 4) { 15 | console.log('Usage: node raw encoder text [options ...]'); 16 | process.exit(1); 17 | } 18 | 19 | let sym = process.argv[2]; 20 | let text = process.argv[3]; 21 | let opts = process.argv[4] || ''; 22 | for (let i = 5; i < process.argv.length; i++) { 23 | opts += ' ' + process.argv[i]; 24 | } 25 | 26 | let stack = bwipjs.raw(sym, text, opts); 27 | for (let i = 0; i < stack.length; i++) { 28 | console.log('' + i + ':'); 29 | let obj = stack[i]; 30 | for (let id in obj) { 31 | let val = obj[id]; 32 | if (val instanceof Array) { 33 | console.log(' ' + id + ': [ ' + val.join(',') + ' ]'); 34 | } else { 35 | console.log(' ' + id + ': ' + val); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | // file : bwip-js/server.js 2 | // 3 | // Simple node HTTP server that renders bar code images using bwip-js. 4 | // 5 | // Usage: node server [address:port] ... 6 | // 7 | // To specify all interfaces, use * as the address 8 | // 9 | // If no address:port are specified, the default is: *:3030 10 | // 11 | "use strict"; 12 | 13 | const http = require('http'); 14 | const url = require('url'); 15 | const bwipjs = (function() { 16 | try { 17 | return require('bwip-js'); // for installed usage 18 | } catch (e) { 19 | return require('..'); // for development use only 20 | } 21 | })(); 22 | 23 | console.log('bwip-js ' + bwipjs.BWIPJS_VERSION + ' / BWIPP ' + bwipjs.BWIPP_VERSION); 24 | 25 | // Optionally, load custom fonts. This shows how to load the Inconsolata font, 26 | // supplied with the bwip-js distribution. The path to your fonts will be different. 27 | // The second and third params, the width and height size multipliers, allow scaling 28 | // a font to the BWIPP built-in font metrics. 29 | // 100 (100%) indicates to use the font's default size. 30 | //bwipjs.loadFont('Inconsolata', 100, 31 | // require('fs').readFileSync(__dirname + '/fonts/Inconsolata.otf', 'binary')); 32 | 33 | const server = http.createServer(function(req, res) { 34 | // If the url does not begin /?bcid= then 404. Otherwise, we end up 35 | // returning 400 on requests like favicon.ico. 36 | if (req.url.indexOf('/?bcid=') != 0) { 37 | res.writeHead(404, { 'Content-Type':'text/plain' }); 38 | res.end('BWIP-JS: Unknown request format.', 'utf8'); 39 | } else { 40 | bwipjs.request(req, res, { sizelimit:1024*1024 }); // limit image size 41 | } 42 | }) 43 | 44 | let binds = 0; 45 | for (let i = 2; i < process.argv.length; i++) { 46 | let a = /^([^:]+):(\d+)$/.exec(process.argv[i]); 47 | if (a) { 48 | if (a[1] == '*') { 49 | server.listen(+a[2]); 50 | } else { 51 | server.listen(+a[2], a[1]); 52 | } 53 | } else { 54 | console.log(process.argv[i] + ': option ignored...'); 55 | } 56 | console.log('listening on ' + process.argv[i]); 57 | binds++; 58 | } 59 | if (!binds) { 60 | server.listen(process.env.PORT || 3030); 61 | console.log('listening on *:' + (process.env.PORT || 3030)); 62 | } 63 | -------------------------------------------------------------------------------- /examples/threaded.js: -------------------------------------------------------------------------------- 1 | // bwip-js/examples/threaded.js 2 | // 3 | // Threaded node.js HTTP server that renders bar code images using bwip-js. 4 | // 5 | // Node 10.5+ only. 6 | // 7 | // Node 10.5 Usage: 8 | // node --experimental-worker threaded [address:port] ... 9 | // 10 | // Node 11+ 11 | // node threaded [address:port] ... 12 | // 13 | // To specify all interfaces, use * as the address 14 | // 15 | // If no address:port are specified, the default is: *:3030 16 | "use strict"; 17 | 18 | const Worker = require('worker_threads').Worker; 19 | const http = require('http'); 20 | const ncpus = require('os').cpus().length; 21 | 22 | // Use arrays for simplicity. They shouldn't get so big as to make O(n) perf matter. 23 | const workers = []; 24 | const queue = []; 25 | 26 | // Create the worker threads 27 | for (let i = 0, n = ncpus-1 || 1; i < n; i++) { 28 | createWorker(); 29 | } 30 | 31 | const server = http.createServer(function(req, res) { 32 | // If the url does not begin /?bcid= then 404. Otherwise, we end up 33 | // returning 400 on requests like favicon.ico. 34 | if (req.url.indexOf('/?bcid=') != 0) { 35 | res.writeHead(404, { 'Content-Type':'text/plain' }); 36 | res.end('BWIP-JS: Unknown request format.', 'utf8'); 37 | } else { 38 | // Some arbitrary limit... 39 | if (queue.length > 16) { 40 | st.res.writeHead(503, { 'Content-Type':'text/plain' }); 41 | st.res.end('HTTP ERROR 503 - Service Unavailable'); 42 | } else if (!workers.length) { 43 | queue.push({ res:res, req:req }); 44 | } else { 45 | let worker = workers.pop(); 46 | worker.workerState = { res:res, req:req }; 47 | worker.postMessage(req.url); 48 | } 49 | } 50 | }) 51 | 52 | let binds = 0; 53 | for (let i = 2; i < process.argv.length; i++) { 54 | let a = /^([^:]+):(\d+)$/.exec(process.argv[i]); 55 | if (a) { 56 | if (a[1] == '*') { 57 | server.listen(+a[2]); 58 | } else { 59 | server.listen(+a[2], a[1]); 60 | } 61 | } else { 62 | console.log(process.argv[i] + ': option ignored...'); 63 | } 64 | console.log('listening on ' + process.argv[i]); 65 | binds++; 66 | } 67 | if (!binds) { 68 | server.listen(process.env.PORT || 3030); 69 | console.log('listening on *:' + (process.env.PORT || 3030)); 70 | } 71 | 72 | function createWorker() { 73 | let worker = new Worker(workerCode(), { eval:true }); 74 | worker.on('error', (err) => { console.log(err); setTimeout(createWorker, 500) }); 75 | worker.on('close', () => setTimeout(createWorker, 500)); 76 | worker.on('online', () => workers.push(worker)); 77 | worker.on('message', (msg) => { 78 | let st = worker.workerState; 79 | 80 | if (msg.err) { 81 | st.res.writeHead(400, { 'Content-Type':'text/plain' }); 82 | st.res.end('' + msg.err, 'utf-8'); 83 | } else if (msg.png) { 84 | st.res.writeHead(200, { 'Content-Type':'image/png' }); 85 | st.res.end(Buffer.from(msg.png), 'binary'); 86 | } else { 87 | // ??? 88 | } 89 | 90 | worker.workerState = null; 91 | if (queue.length) { 92 | worker.workerState = queue.shift(); 93 | worker.postMessage(worker.workerState.req.url); 94 | } 95 | }); 96 | } 97 | 98 | function workerCode() { 99 | return ` 100 | const worker = require('worker_threads'); 101 | const url = require('url'); 102 | const bwipjs = (function() { 103 | try { 104 | return require('bwip-js'); // for installed usage 105 | } catch (e) { 106 | return require('..'); // for development use only 107 | } 108 | })(); 109 | 110 | worker.parentPort.on('message', (requestUrl) => { 111 | let opts = url.parse(requestUrl, true).query; 112 | 113 | // Convert empty parameters to true. 114 | // Convert empty !parameters to false. 115 | for (let id in opts) { 116 | if (opts[id] === '') { 117 | if (id[0] == '!') { 118 | opts[id.substr(1)] = false; 119 | } else { 120 | opts[id] = true; 121 | } 122 | } 123 | } 124 | 125 | bwipjs.toBuffer(opts, function(err, png) { 126 | if (err) { 127 | worker.parentPort.postMessage({ err:'' + (err.stack || err) }); 128 | } else { 129 | worker.parentPort.postMessage({ png:png.buffer }, [ png.buffer ]); 130 | } 131 | }); 132 | }); 133 | `; 134 | 135 | } 136 | -------------------------------------------------------------------------------- /fonts/Inconsolata.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metafloor/bwip-js/33e5198e66a96639ef1a47c95ed2e363b2b79852/fonts/Inconsolata.otf -------------------------------------------------------------------------------- /fonts/OCRA7.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metafloor/bwip-js/33e5198e66a96639ef1a47c95ed2e363b2b79852/fonts/OCRA7.ttf -------------------------------------------------------------------------------- /fonts/OCRB7.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metafloor/bwip-js/33e5198e66a96639ef1a47c95ed2e363b2b79852/fonts/OCRB7.ttf -------------------------------------------------------------------------------- /lib/canvas-toblob.js: -------------------------------------------------------------------------------- 1 | /* canvas-toBlob.js 2 | * A canvas.toBlob() implementation. 3 | * 2013-12-27 4 | * 5 | * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr 6 | * License: X11/MIT 7 | * See https://github.com/eligrey/canvas-toBlob.js/blob/master/LICENSE.md 8 | */ 9 | 10 | /*global self */ 11 | /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, 12 | plusplus: true */ 13 | 14 | /*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */ 15 | 16 | (function(view) { 17 | "use strict"; 18 | var 19 | Uint8Array = view.Uint8Array 20 | , HTMLCanvasElement = view.HTMLCanvasElement 21 | , canvas_proto = HTMLCanvasElement && HTMLCanvasElement.prototype 22 | , is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i 23 | , to_data_url = "toDataURL" 24 | , base64_ranks 25 | , decode_base64 = function(base64) { 26 | var 27 | len = base64.length 28 | , buffer = new Uint8Array(len / 4 * 3 | 0) 29 | , i = 0 30 | , outptr = 0 31 | , last = [0, 0] 32 | , state = 0 33 | , save = 0 34 | , rank 35 | , code 36 | , undef 37 | ; 38 | while (len--) { 39 | code = base64.charCodeAt(i++); 40 | rank = base64_ranks[code-43]; 41 | if (rank !== 255 && rank !== undef) { 42 | last[1] = last[0]; 43 | last[0] = code; 44 | save = (save << 6) | rank; 45 | state++; 46 | if (state === 4) { 47 | buffer[outptr++] = save >>> 16; 48 | if (last[1] !== 61 /* padding character */) { 49 | buffer[outptr++] = save >>> 8; 50 | } 51 | if (last[0] !== 61 /* padding character */) { 52 | buffer[outptr++] = save; 53 | } 54 | state = 0; 55 | } 56 | } 57 | } 58 | // 2/3 chance there's going to be some null bytes at the end, but that 59 | // doesn't really matter with most image formats. 60 | // If it somehow matters for you, truncate the buffer up outptr. 61 | return buffer; 62 | } 63 | ; 64 | if (Uint8Array) { 65 | base64_ranks = new Uint8Array([ 66 | 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1 67 | , -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 68 | , 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 69 | , -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 70 | , 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 71 | ]); 72 | } 73 | if (HTMLCanvasElement && !canvas_proto.toBlob) { 74 | canvas_proto.toBlob = function(callback, type /*, ...args*/) { 75 | if (!type) { 76 | type = "image/png"; 77 | } if (this.mozGetAsFile) { 78 | callback(this.mozGetAsFile("canvas", type)); 79 | return; 80 | } if (this.msToBlob && /^\s*image\/png\s*(?:$|;)/i.test(type)) { 81 | callback(this.msToBlob()); 82 | return; 83 | } 84 | 85 | var 86 | args = Array.prototype.slice.call(arguments, 1) 87 | , dataURI = this[to_data_url].apply(this, args) 88 | , header_end = dataURI.indexOf(",") 89 | , data = dataURI.substring(header_end + 1) 90 | , is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)) 91 | , blob 92 | ; 93 | if (Blob.fake) { 94 | // no reason to decode a data: URI that's just going to become a data URI again 95 | blob = new Blob 96 | if (is_base64) { 97 | blob.encoding = "base64"; 98 | } else { 99 | blob.encoding = "URI"; 100 | } 101 | blob.data = data; 102 | blob.size = data.length; 103 | } else if (Uint8Array) { 104 | if (is_base64) { 105 | blob = new Blob([decode_base64(data)], {type: type}); 106 | } else { 107 | blob = new Blob([decodeURIComponent(data)], {type: type}); 108 | } 109 | } 110 | callback(blob); 111 | }; 112 | 113 | if (canvas_proto.toDataURLHD) { 114 | canvas_proto.toBlobHD = function() { 115 | to_data_url = "toDataURLHD"; 116 | var blob = this.toBlob(); 117 | to_data_url = "toDataURL"; 118 | return blob; 119 | } 120 | } else { 121 | canvas_proto.toBlobHD = canvas_proto.toBlob; 122 | } 123 | } 124 | }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); 125 | -------------------------------------------------------------------------------- /lib/demo.css: -------------------------------------------------------------------------------- 1 | /* bwip-js : lib/demo.css */ 2 | body { 3 | font-size: 11pt; 4 | font-family: "Lucida Grande",Calibri,Helvetica,Arial,sans-serif; 5 | margin: 0px; 6 | } 7 | #header { 8 | position: relative; 9 | background-color: #212121; 10 | color: #fff; 11 | height: 48px; 12 | padding-left: 21px; 13 | padding-top: 0px; 14 | padding-bottom: 20px; 15 | margin-bottom: 17px; 16 | } 17 | #header #bwip-js { 18 | font-size: 25px; 19 | font-family: "Lucida Grande",Calibri,Helvetica,Arial,sans-serif; 20 | font-weight: bold; 21 | position: absolute; 22 | bottom: 21px; 23 | } 24 | #header #versions { 25 | font-size: 10px; 26 | font-family: "Lucida Grande",Calibri,Helvetica,Arial,sans-serif; 27 | font-weight: 100; 28 | color: #a7a7a7; 29 | position: absolute; 30 | bottom: 2px; 31 | right: 12px; 32 | } 33 | input, select, label, button { 34 | font-family: Verdana, Arial, san-serif; 35 | font-size: 10.5pt; 36 | } 37 | #symbol, #symtext, #symaltx, #symopts, #scaleX, #scaleY { 38 | padding: .5ex; 39 | box-sizing: border-box; 40 | border: 1px solid #999; 41 | border-radius: 4px; 42 | } 43 | #symbol, #symtext, #symaltx, #symopts, #scaleX, #scaleY { 44 | width: 63ex; 45 | } 46 | #scaleX, #scaleY { 47 | width: 6ex; 48 | 49 | } 50 | #fonthdr { 51 | position: absolute; 52 | bottom: 0px; 53 | left: 556px; 54 | width: 16ex; 55 | padding-left: 2ex; 56 | padding-top: 1ex; 57 | padding-bottom: 1ex; 58 | background-color: #0090ff; 59 | border-top-left-radius: 4px; 60 | border-top-right-radius: 4px; 61 | color: white; 62 | font-family: Verdana, Arial, san-serif; 63 | font-weight: bold; 64 | cursor: default; 65 | } 66 | #fonthdr:hover { 67 | background-color: #32aaff; 68 | } 69 | #fontdiv { 70 | z-index: 999; 71 | position: absolute; 72 | top: 68px; 73 | left: 556px; 74 | padding: 0px; 75 | margin: 0px; 76 | border: 0px; 77 | } 78 | #fontdiv div.inner { 79 | border: 1px solid #b9c3ff; 80 | border-top: 0px; 81 | border-bottom-left-radius: 3px; 82 | border-bottom-right-radius: 3px; 83 | padding: 16px; 84 | color: #000; 85 | background-color: #e3eeff; 86 | font-family: Verdana, Arial, san-serif; 87 | x-box-shadow: 2px 2px 3px #ddd; 88 | } 89 | #dropzone { 90 | width: 472px; 91 | text-align: center; 92 | border: 1px dashed #999; 93 | background-color: #e7e7e7; 94 | color: #777; 95 | overflow: hidden; 96 | } 97 | #dropzone:hover { 98 | border: 1px dashed #777; 99 | color: #555; 100 | background-color: #eeeeee; 101 | } 102 | #dropzone div.content { 103 | padding: 32px 96px; 104 | } 105 | #dropzone .fd-file { 106 | opacity: 0; 107 | font-size: 72px; 108 | position: absolute; 109 | right: 0px; 110 | top: 0px; 111 | width: 1000px; 112 | } 113 | #fontlist.visible { 114 | margin-top: 20px; 115 | border-top: 1px solid #bbb; 116 | padding-top: 20px; 117 | } 118 | #fontlist td, #fontlist th { 119 | text-align: left; 120 | padding: 3px 1ex; 121 | border-bottom: 1px solid #999; 122 | } 123 | #fontlist td.delete span { 124 | opacity: .4; 125 | } 126 | #fontlist td.delete:hover span { 127 | opacity: 1; 128 | } 129 | #params { 130 | margin-left: 12px; 131 | } 132 | #content { 133 | margin-left: 20px; 134 | } 135 | #params th { 136 | text-align: right; 137 | padding-right: 1.5ex; 138 | } 139 | a.saveas { 140 | color: blue; 141 | } 142 | a.saveas:visited { 143 | color: blue; 144 | } 145 | button { 146 | padding: .5ex 1ex; 147 | min-width: 18ex; 148 | } 149 | -------------------------------------------------------------------------------- /lib/filesaver.js: -------------------------------------------------------------------------------- 1 | /* FileSaver.js 2 | * A saveAs() FileSaver implementation. 3 | * 2015-03-04 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * License: X11/MIT 7 | * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md 8 | */ 9 | 10 | /*global self */ 11 | /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 12 | 13 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 14 | 15 | var saveAs = saveAs 16 | // IE 10+ (native saveAs) 17 | || (typeof navigator !== "undefined" && 18 | navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator)) 19 | // Everyone else 20 | || (function(view) { 21 | "use strict"; 22 | // IE <10 is explicitly unsupported 23 | if (typeof navigator !== "undefined" && 24 | /MSIE [1-9]\./.test(navigator.userAgent)) { 25 | return; 26 | } 27 | var 28 | doc = view.document 29 | // only get URL when necessary in case Blob.js hasn't overridden it yet 30 | , get_URL = function() { 31 | return view.URL || view.webkitURL || view; 32 | } 33 | , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") 34 | , can_use_save_link = "download" in save_link 35 | , click = function(node) { 36 | var event = doc.createEvent("MouseEvents"); 37 | event.initMouseEvent( 38 | "click", true, false, view, 0, 0, 0, 0, 0 39 | , false, false, false, false, 0, null 40 | ); 41 | node.dispatchEvent(event); 42 | } 43 | , webkit_req_fs = view.webkitRequestFileSystem 44 | , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem 45 | , throw_outside = function(ex) { 46 | (view.setImmediate || view.setTimeout)(function() { 47 | throw ex; 48 | }, 0); 49 | } 50 | , force_saveable_type = "application/octet-stream" 51 | , fs_min_size = 0 52 | // See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and 53 | // https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047 54 | // for the reasoning behind the timeout and revocation flow 55 | , arbitrary_revoke_timeout = 500 // in ms 56 | , revoke = function(file) { 57 | var revoker = function() { 58 | if (typeof file === "string") { // file is an object URL 59 | get_URL().revokeObjectURL(file); 60 | } else { // file is a File 61 | file.remove(); 62 | } 63 | }; 64 | if (view.chrome) { 65 | revoker(); 66 | } else { 67 | setTimeout(revoker, arbitrary_revoke_timeout); 68 | } 69 | } 70 | , dispatch = function(filesaver, event_types, event) { 71 | event_types = [].concat(event_types); 72 | var i = event_types.length; 73 | while (i--) { 74 | var listener = filesaver["on" + event_types[i]]; 75 | if (typeof listener === "function") { 76 | try { 77 | listener.call(filesaver, event || filesaver); 78 | } catch (ex) { 79 | throw_outside(ex); 80 | } 81 | } 82 | } 83 | } 84 | , FileSaver = function(blob, name) { 85 | // First try a.download, then web filesystem, then object URLs 86 | var 87 | filesaver = this 88 | , type = blob.type 89 | , blob_changed = false 90 | , object_url 91 | , target_view 92 | , dispatch_all = function() { 93 | dispatch(filesaver, "writestart progress write writeend".split(" ")); 94 | } 95 | // on any filesys errors revert to saving with object URLs 96 | , fs_error = function() { 97 | // don't create more object URLs than needed 98 | if (blob_changed || !object_url) { 99 | object_url = get_URL().createObjectURL(blob); 100 | } 101 | if (target_view) { 102 | target_view.location.href = object_url; 103 | } else { 104 | var new_tab = view.open(object_url, "_blank"); 105 | if (new_tab == undefined && typeof safari !== "undefined") { 106 | //Apple do not allow window.open, see http://bit.ly/1kZffRI 107 | view.location.href = object_url 108 | } 109 | } 110 | filesaver.readyState = filesaver.DONE; 111 | dispatch_all(); 112 | revoke(object_url); 113 | } 114 | , abortable = function(func) { 115 | return function() { 116 | if (filesaver.readyState !== filesaver.DONE) { 117 | return func.apply(this, arguments); 118 | } 119 | }; 120 | } 121 | , create_if_not_found = {create: true, exclusive: false} 122 | , slice 123 | ; 124 | filesaver.readyState = filesaver.INIT; 125 | if (!name) { 126 | name = "download"; 127 | } 128 | if (can_use_save_link) { 129 | object_url = get_URL().createObjectURL(blob); 130 | save_link.href = object_url; 131 | save_link.download = name; 132 | click(save_link); 133 | filesaver.readyState = filesaver.DONE; 134 | dispatch_all(); 135 | revoke(object_url); 136 | return; 137 | } 138 | // prepend BOM for UTF-8 XML and text/plain types 139 | if (/^\s*(?:text\/(?:plain|xml)|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 140 | blob = new Blob(["\ufeff", blob], {type: blob.type}); 141 | } 142 | // Object and web filesystem URLs have a problem saving in Google Chrome when 143 | // viewed in a tab, so I force save with application/octet-stream 144 | // http://code.google.com/p/chromium/issues/detail?id=91158 145 | // Update: Google errantly closed 91158, I submitted it again: 146 | // https://code.google.com/p/chromium/issues/detail?id=389642 147 | if (view.chrome && type && type !== force_saveable_type) { 148 | slice = blob.slice || blob.webkitSlice; 149 | blob = slice.call(blob, 0, blob.size, force_saveable_type); 150 | blob_changed = true; 151 | } 152 | // Since I can't be sure that the guessed media type will trigger a download 153 | // in WebKit, I append .download to the filename. 154 | // https://bugs.webkit.org/show_bug.cgi?id=65440 155 | if (webkit_req_fs && name !== "download") { 156 | name += ".download"; 157 | } 158 | if (type === force_saveable_type || webkit_req_fs) { 159 | target_view = view; 160 | } 161 | if (!req_fs) { 162 | fs_error(); 163 | return; 164 | } 165 | fs_min_size += blob.size; 166 | req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { 167 | fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { 168 | var save = function() { 169 | dir.getFile(name, create_if_not_found, abortable(function(file) { 170 | file.createWriter(abortable(function(writer) { 171 | writer.onwriteend = function(event) { 172 | target_view.location.href = file.toURL(); 173 | filesaver.readyState = filesaver.DONE; 174 | dispatch(filesaver, "writeend", event); 175 | revoke(file); 176 | }; 177 | writer.onerror = function() { 178 | var error = writer.error; 179 | if (error.code !== error.ABORT_ERR) { 180 | fs_error(); 181 | } 182 | }; 183 | "writestart progress write abort".split(" ").forEach(function(event) { 184 | writer["on" + event] = filesaver["on" + event]; 185 | }); 186 | writer.write(blob); 187 | filesaver.abort = function() { 188 | writer.abort(); 189 | filesaver.readyState = filesaver.DONE; 190 | }; 191 | filesaver.readyState = filesaver.WRITING; 192 | }), fs_error); 193 | }), fs_error); 194 | }; 195 | dir.getFile(name, {create: false}, abortable(function(file) { 196 | // delete file if it already exists 197 | file.remove(); 198 | save(); 199 | }), abortable(function(ex) { 200 | if (ex.code === ex.NOT_FOUND_ERR) { 201 | save(); 202 | } else { 203 | fs_error(); 204 | } 205 | })); 206 | }), fs_error); 207 | }), fs_error); 208 | } 209 | , FS_proto = FileSaver.prototype 210 | , saveAs = function(blob, name) { 211 | return new FileSaver(blob, name); 212 | } 213 | ; 214 | FS_proto.abort = function() { 215 | var filesaver = this; 216 | filesaver.readyState = filesaver.DONE; 217 | dispatch(filesaver, "abort"); 218 | }; 219 | FS_proto.readyState = FS_proto.INIT = 0; 220 | FS_proto.WRITING = 1; 221 | FS_proto.DONE = 2; 222 | 223 | FS_proto.error = 224 | FS_proto.onwritestart = 225 | FS_proto.onprogress = 226 | FS_proto.onwrite = 227 | FS_proto.onabort = 228 | FS_proto.onerror = 229 | FS_proto.onwriteend = 230 | null; 231 | 232 | return saveAs; 233 | }( 234 | typeof self !== "undefined" && self 235 | || typeof window !== "undefined" && window 236 | || this.content 237 | )); 238 | // `self` is undefined in Firefox for Android content script context 239 | // while `this` is nsIContentFrameMessageManager 240 | // with an attribute `content` that corresponds to the window 241 | 242 | if (typeof module !== "undefined" && module.exports) { 243 | module.exports.saveAs = saveAs; 244 | } else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) { 245 | define([], function() { 246 | return saveAs; 247 | }); 248 | } 249 | -------------------------------------------------------------------------------- /lib/symdesc.js: -------------------------------------------------------------------------------- 1 | // file: bwip-js/lib/symdesc.js 2 | // 3 | // This code was automatically generated from: 4 | // Barcode Writer in Pure PostScript - Version 2025-04-19 5 | // 6 | // Copyright (c) 2011-2025 Mark Warren 7 | // Copyright (c) 2004-2024 Terry Burton 8 | // 9 | // Licensed MIT. See the LICENSE file in the bwip-js root directory 10 | // for the extended copyright notice. 11 | var symdesc = { 12 | "ean5":{ sym:"ean5",desc:"EAN-5 (5 digit addon)",text:"90200",opts:"includetext guardwhitespace" }, 13 | "ean2":{ sym:"ean2",desc:"EAN-2 (2 digit addon)",text:"05",opts:"includetext guardwhitespace" }, 14 | "ean13":{ sym:"ean13",desc:"EAN-13",text:"9520123456788",opts:"includetext guardwhitespace" }, 15 | "ean8":{ sym:"ean8",desc:"EAN-8",text:"95200002",opts:"includetext guardwhitespace" }, 16 | "upca":{ sym:"upca",desc:"UPC-A",text:"012345000058",opts:"includetext" }, 17 | "upce":{ sym:"upce",desc:"UPC-E",text:"01234558",opts:"includetext" }, 18 | "isbn":{ sym:"isbn",desc:"ISBN",text:"978-1-56581-231-4 90000",opts:"includetext guardwhitespace" }, 19 | "ismn":{ sym:"ismn",desc:"ISMN",text:"979-0-2605-3211-3",opts:"includetext guardwhitespace" }, 20 | "issn":{ sym:"issn",desc:"ISSN",text:"0311-175X 00 17",opts:"includetext guardwhitespace" }, 21 | "mands":{ sym:"mands",desc:"Marks & Spencer",text:"0642118",opts:"includetext" }, 22 | "code128":{ sym:"code128",desc:"Code 128",text:"Count01234567!",opts:"includetext" }, 23 | "gs1-128":{ sym:"gs1-128",desc:"GS1-128",text:"(01)09521234543213(3103)000123",opts:"includetext" }, 24 | "ean14":{ sym:"ean14",desc:"EAN-14",text:"(01) 0 952 8765 43210 8",opts:"includetext" }, 25 | "sscc18":{ sym:"sscc18",desc:"SSCC-18",text:"(00) 0 9528765 432101234 6",opts:"includetext" }, 26 | "code39":{ sym:"code39",desc:"Code 39",text:"THIS IS CODE 39",opts:"includetext includecheck includecheckintext" }, 27 | "code39ext":{ sym:"code39ext",desc:"Code 39 Extended",text:"Code39 Ext!",opts:"includetext includecheck includecheckintext" }, 28 | "code32":{ sym:"code32",desc:"Italian Pharmacode",text:"01234567",opts:"includetext" }, 29 | "pzn":{ sym:"pzn",desc:"Pharmazentralnummer (PZN)",text:"123456",opts:"includetext" }, 30 | "code93":{ sym:"code93",desc:"Code 93",text:"THIS IS CODE 93",opts:"includetext includecheck" }, 31 | "code93ext":{ sym:"code93ext",desc:"Code 93 Extended",text:"Code93 Ext!",opts:"includetext includecheck" }, 32 | "interleaved2of5":{ sym:"interleaved2of5",desc:"Interleaved 2 of 5 (ITF)",text:"2401234567",opts:"height=12 includecheck includetext includecheckintext" }, 33 | "itf14":{ sym:"itf14",desc:"ITF-14",text:"0 952 1234 54321 3",opts:"includetext" }, 34 | "identcode":{ sym:"identcode",desc:"Deutsche Post Identcode",text:"563102430313",opts:"includetext" }, 35 | "leitcode":{ sym:"leitcode",desc:"Deutsche Post Leitcode",text:"21348075016401",opts:"includetext" }, 36 | "databaromni":{ sym:"databaromni",desc:"GS1 DataBar Omnidirectional",text:"(01)09521234543213",opts:"" }, 37 | "databarstacked":{ sym:"databarstacked",desc:"GS1 DataBar Stacked",text:"(01)09521234543213",opts:"" }, 38 | "databarstackedomni":{ sym:"databarstackedomni",desc:"GS1 DataBar Stacked Omnidirectional",text:"(01)24012345678905",opts:"" }, 39 | "databartruncated":{ sym:"databartruncated",desc:"GS1 DataBar Truncated",text:"(01)09521234543213",opts:"" }, 40 | "databarlimited":{ sym:"databarlimited",desc:"GS1 DataBar Limited",text:"(01)09521234543213",opts:"" }, 41 | "databarexpanded":{ sym:"databarexpanded",desc:"GS1 DataBar Expanded",text:"(01)09521234543213(3103)000123",opts:"" }, 42 | "databarexpandedstacked":{ sym:"databarexpandedstacked",desc:"GS1 DataBar Expanded Stacked",text:"(01)09521234543213(3103)000123",opts:"segments=4" }, 43 | "gs1northamericancoupon":{ sym:"gs1northamericancoupon",desc:"GS1 North American Coupon",text:"(8110)106141416543213500110000310123196000",opts:"includetext segments=8" }, 44 | "pharmacode":{ sym:"pharmacode",desc:"Pharmaceutical Binary Code",text:"117480",opts:"showborder" }, 45 | "pharmacode2":{ sym:"pharmacode2",desc:"Two-track Pharmacode",text:"117480",opts:"includetext showborder" }, 46 | "code2of5":{ sym:"code2of5",desc:"Code 25",text:"01234567",opts:"includetext includecheck includecheckintext" }, 47 | "industrial2of5":{ sym:"industrial2of5",desc:"Industrial 2 of 5",text:"01234567",opts:"includetext includecheck includecheckintext" }, 48 | "iata2of5":{ sym:"iata2of5",desc:"IATA 2 of 5",text:"01234567",opts:"includetext includecheck includecheckintext" }, 49 | "matrix2of5":{ sym:"matrix2of5",desc:"Matrix 2 of 5",text:"01234567",opts:"includetext includecheck includecheckintext" }, 50 | "coop2of5":{ sym:"coop2of5",desc:"COOP 2 of 5",text:"01234567",opts:"includetext includecheck includecheckintext" }, 51 | "datalogic2of5":{ sym:"datalogic2of5",desc:"Datalogic 2 of 5",text:"01234567",opts:"includetext includecheck includecheckintext" }, 52 | "code11":{ sym:"code11",desc:"Code 11",text:"0123456789",opts:"includetext includecheck includecheckintext" }, 53 | "bc412":{ sym:"bc412",desc:"BC412",text:"BC412SEMI",opts:"semi includetext includecheckintext" }, 54 | "rationalizedCodabar":{ sym:"rationalizedCodabar",desc:"Codabar",text:"A0123456789B",opts:"includetext includecheck includecheckintext" }, 55 | "onecode":{ sym:"onecode",desc:"USPS Intelligent Mail",text:"0123456709498765432101234567891",opts:"barcolor=FF0000" }, 56 | "postnet":{ sym:"postnet",desc:"USPS POSTNET",text:"01234",opts:"includetext includecheckintext" }, 57 | "planet":{ sym:"planet",desc:"USPS PLANET",text:"01234567890",opts:"includetext includecheckintext" }, 58 | "royalmail":{ sym:"royalmail",desc:"Royal Mail 4 State Customer Code",text:"LE28HS9Z",opts:"includetext barcolor=FF0000" }, 59 | "auspost":{ sym:"auspost",desc:"AusPost 4 State Customer Code",text:"5956439111ABA 9",opts:"includetext custinfoenc=character" }, 60 | "kix":{ sym:"kix",desc:"Royal Dutch TPG Post KIX",text:"1231FZ13XHS",opts:"includetext" }, 61 | "japanpost":{ sym:"japanpost",desc:"Japan Post 4 State Customer Code",text:"6540123789-A-K-Z",opts:"includetext includecheckintext" }, 62 | "msi":{ sym:"msi",desc:"MSI Modified Plessey",text:"0123456789",opts:"includetext includecheck includecheckintext" }, 63 | "plessey":{ sym:"plessey",desc:"Plessey UK",text:"01234ABCD",opts:"includetext includecheckintext" }, 64 | "telepen":{ sym:"telepen",desc:"Telepen",text:"ABCDEF",opts:"includetext" }, 65 | "telepennumeric":{ sym:"telepennumeric",desc:"Telepen Numeric",text:"01234567",opts:"includetext" }, 66 | "posicode":{ sym:"posicode",desc:"PosiCode",text:"ABC123",opts:"version=b inkspread=-0.5 parsefnc includetext" }, 67 | "codablockf":{ sym:"codablockf",desc:"Codablock F",text:"CODABLOCK F 34567890123456789010040digit",opts:"columns=8" }, 68 | "code16k":{ sym:"code16k",desc:"Code 16K",text:"Abcd-1234567890-wxyZ",opts:"" }, 69 | "code49":{ sym:"code49",desc:"Code 49",text:"MULTIPLE ROWS IN CODE 49",opts:"" }, 70 | "channelcode":{ sym:"channelcode",desc:"Channel Code",text:"3493",opts:"height=12 includetext" }, 71 | "flattermarken":{ sym:"flattermarken",desc:"Flattermarken",text:"11099",opts:"inkspread=-0.25 showborder borderleft=0 borderright=0" }, 72 | "raw":{ sym:"raw",desc:"Custom 1D symbology",text:"331132131313411122131311333213114131131221323",opts:"height=12" }, 73 | "daft":{ sym:"daft",desc:"Custom 4 state symbology",text:"FATDAFTDAD",opts:"" }, 74 | "symbol":{ sym:"symbol",desc:"Miscellaneous symbols",text:"fima",opts:"backgroundcolor=DD000011" }, 75 | "pdf417":{ sym:"pdf417",desc:"PDF417",text:"This is PDF417",opts:"columns=2" }, 76 | "pdf417compact":{ sym:"pdf417compact",desc:"Compact PDF417",text:"This is compact PDF417",opts:"columns=2" }, 77 | "micropdf417":{ sym:"micropdf417",desc:"MicroPDF417",text:"MicroPDF417",opts:"" }, 78 | "datamatrix":{ sym:"datamatrix",desc:"Data Matrix",text:"This is Data Matrix!",opts:"" }, 79 | "datamatrixrectangular":{ sym:"datamatrixrectangular",desc:"Data Matrix Rectangular",text:"1234",opts:"" }, 80 | "datamatrixrectangularextension":{ sym:"datamatrixrectangularextension",desc:"Data Matrix Rectangular Extension",text:"1234",opts:"version=8x96" }, 81 | "mailmark":{ sym:"mailmark",desc:"Royal Mail Mailmark",text:"JGB 012100123412345678AB19XY1A 0 www.xyz.com",opts:"type=29" }, 82 | "qrcode":{ sym:"qrcode",desc:"QR Code",text:"http://goo.gl/0bis",opts:"eclevel=M" }, 83 | "swissqrcode":{ sym:"swissqrcode",desc:"Swiss QR Code",text:"SPC^CR^LF0200^CR^LF1^CR^LFCH5800791123000889012^CR^LFS^CR^LFRobert Schneider AG^CR^LFRue du Lac^CR^LF1268^CR^LF2501^CR^LFBiel^CR^LFCH^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF^CR^LF199.95^CR^LFCHF^CR^LFK^CR^LFPia-Maria Rutschmann-Schnyder^CR^LFGrosse Marktgasse 28^CR^LF9400 Rorschach^CR^LF^CR^LF^CR^LFCH^CR^LFSCOR^CR^LFRF18539007547034^CR^LF^CR^LFEPD",opts:"parse" }, 84 | "microqrcode":{ sym:"microqrcode",desc:"Micro QR Code",text:"1234",opts:"" }, 85 | "rectangularmicroqrcode":{ sym:"rectangularmicroqrcode",desc:"Rectangular Micro QR Code",text:"1234",opts:"version=R17x139" }, 86 | "maxicode":{ sym:"maxicode",desc:"MaxiCode",text:"[)>^03001^02996152382802^029840^029001^0291Z00004951^029UPSN^02906X610^029159^0291234567^0291/1^029^029Y^029634 ALPHA DR^029PITTSBURGH^029PA^029^004",opts:"mode=2 parse" }, 87 | "azteccode":{ sym:"azteccode",desc:"Aztec Code",text:"This is Aztec Code",opts:"format=full" }, 88 | "azteccodecompact":{ sym:"azteccodecompact",desc:"Compact Aztec Code",text:"1234",opts:"" }, 89 | "aztecrune":{ sym:"aztecrune",desc:"Aztec Runes",text:"1",opts:"" }, 90 | "codeone":{ sym:"codeone",desc:"Code One",text:"Code One",opts:"" }, 91 | "hanxin":{ sym:"hanxin",desc:"Han Xin Code",text:"This is Han Xin",opts:"" }, 92 | "dotcode":{ sym:"dotcode",desc:"DotCode",text:"This is DotCode",opts:"inkspread=0.16" }, 93 | "ultracode":{ sym:"ultracode",desc:"Ultracode",text:"Awesome colours!",opts:"eclevel=EC2" }, 94 | "gs1-cc":{ sym:"gs1-cc",desc:"GS1 Composite 2D Component",text:"(01)09521234543213(3103)000123",opts:"ccversion=b cccolumns=4" }, 95 | "ean13composite":{ sym:"ean13composite",desc:"EAN-13 Composite",text:"9520123456788|(99)1234-abcd",opts:"includetext" }, 96 | "ean8composite":{ sym:"ean8composite",desc:"EAN-8 Composite",text:"95200002|(21)A12345678",opts:"includetext" }, 97 | "upcacomposite":{ sym:"upcacomposite",desc:"UPC-A Composite",text:"012345000058|(99)1234-abcd",opts:"includetext" }, 98 | "upcecomposite":{ sym:"upcecomposite",desc:"UPC-E Composite",text:"01234558|(15)021231",opts:"includetext" }, 99 | "databaromnicomposite":{ sym:"databaromnicomposite",desc:"GS1 DataBar Omnidirectional Composite",text:"(01)09521234543213|(11)990102",opts:"" }, 100 | "databarstackedcomposite":{ sym:"databarstackedcomposite",desc:"GS1 DataBar Stacked Composite",text:"(01)09521234543213|(17)010200",opts:"" }, 101 | "databarstackedomnicomposite":{ sym:"databarstackedomnicomposite",desc:"GS1 DataBar Stacked Omnidirectional Composite",text:"(01)03612345678904|(11)990102",opts:"" }, 102 | "databartruncatedcomposite":{ sym:"databartruncatedcomposite",desc:"GS1 DataBar Truncated Composite",text:"(01)09521234543213|(11)990102",opts:"" }, 103 | "databarlimitedcomposite":{ sym:"databarlimitedcomposite",desc:"GS1 DataBar Limited Composite",text:"(01)09521234543213|(21)abcdefghijklmnopqrst",opts:"" }, 104 | "databarexpandedcomposite":{ sym:"databarexpandedcomposite",desc:"GS1 DataBar Expanded Composite",text:"(01)09521234543213(3103)001234|(91)1A2B3C4D5E",opts:"" }, 105 | "databarexpandedstackedcomposite":{ sym:"databarexpandedstackedcomposite",desc:"GS1 DataBar Expanded Stacked Composite",text:"(01)09521234543213(10)ABCDEF|(21)12345678",opts:"segments=4" }, 106 | "gs1-128composite":{ sym:"gs1-128composite",desc:"GS1-128 Composite",text:"(00)095287654321012346|(02)09521234543213(37)24(10)1234567ABCDEFG",opts:"ccversion=c" }, 107 | "gs1datamatrix":{ sym:"gs1datamatrix",desc:"GS1 Data Matrix",text:"(01)09521234543213(17)120508(10)ABCD1234(410)9501101020917",opts:"" }, 108 | "gs1datamatrixrectangular":{ sym:"gs1datamatrixrectangular",desc:"GS1 Data Matrix Rectangular",text:"(01)09521234543213(17)120508(10)ABCD1234(410)9501101020917",opts:"" }, 109 | "gs1dldatamatrix":{ sym:"gs1dldatamatrix",desc:"GS1 Digital Link Data Matrix",text:"https://id.gs1.org/01/09521234543213/22/ABC%2D123?99=XYZ-987",opts:"includetext" }, 110 | "gs1qrcode":{ sym:"gs1qrcode",desc:"GS1 QR Code",text:"(01)09521234543213(8200)http://www.abc.net(10)ABCD1234(410)9501101020917",opts:"" }, 111 | "gs1dlqrcode":{ sym:"gs1dlqrcode",desc:"GS1 Digital Link QR Code",text:"HTTPS://ID.GS1.ORG/01/09521234543213/22/ABC%2D123?99=XYZ-987",opts:"includetext" }, 112 | "gs1dotcode":{ sym:"gs1dotcode",desc:"GS1 DotCode",text:"(235)5vBZIF%!", 90 | "license": "MIT", 91 | "bugs": { 92 | "url": "https://github.com/metafloor/bwip-js/issues" 93 | }, 94 | "homepage": "https://github.com/metafloor/bwip-js" 95 | } 96 | -------------------------------------------------------------------------------- /src/bwip-js.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for bwip-js __BWIPJS_VERS__ 2 | // 3 | // THIS DEFINITION FILE IS MACHINE GENERATED - DO NOT EDIT 4 | // 5 | // Project: https://github.com/metafloor/bwip-js 6 | // 7 | // This definition file was based on: 8 | // 9 | // Definitions by: TANAKA Koichi 10 | // Guillaume VanderEst 11 | // Ryan Jentzsch 12 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 13 | 14 | // platform-specific includes 15 | 16 | declare namespace BwipJs { 17 | export interface BwippOptions { 18 | includecheck?: boolean | undefined; 19 | includecheckintext?: boolean | undefined; 20 | 21 | parse?: boolean | undefined; 22 | parsefnc?: boolean | undefined; 23 | 24 | height?: number | undefined; 25 | width?: number | undefined; 26 | 27 | inkspread?: number | undefined; 28 | inkspreadh?: number | undefined; 29 | inkspreadv?: number | undefined; 30 | dotty?: boolean | undefined; 31 | 32 | binarytext?: boolean | undefined; // really a bwip-js option but better positioned here 33 | includetext?: boolean | undefined; 34 | textfont?: string | undefined; 35 | textsize?: number | undefined; 36 | textgaps?: number | undefined; 37 | alttext?: string | undefined; 38 | 39 | textxalign?: 'offleft' | 'left' | 'center' | 'right' | 'offright' | 'justify' | undefined; 40 | textyalign?: 'below' | 'center' | 'above' | undefined; 41 | textxoffset?: number | undefined; 42 | textyoffset?: number | undefined; 43 | 44 | showborder?: boolean | undefined; 45 | borderwidth?: number | undefined; 46 | borderleft?: number | undefined; 47 | borderright?: number | undefined; 48 | bordertop?: number | undefined; 49 | borderbottom?: number | undefined; 50 | 51 | barcolor?: string | undefined; 52 | backgroundcolor?: string | undefined; 53 | bordercolor?: string | undefined; 54 | textcolor?: string | undefined; 55 | 56 | addontextxoffset?: number | undefined; 57 | addontextyoffset?: number | undefined; 58 | addontextfont?: string | undefined; 59 | addontextsize?: number | undefined; 60 | 61 | guardwhitespace?: boolean | undefined; 62 | guardwidth?: number | undefined; 63 | guardheight?: number | undefined; 64 | guardleftpos?: number | undefined; 65 | guardrightpos?: number | undefined; 66 | guardleftypos?: number | undefined; 67 | guardrightypos?: number | undefined; 68 | } 69 | export interface RenderOptions extends BwippOptions { 70 | bcid: string; 71 | text: string; 72 | 73 | scaleX?: number | undefined; 74 | scaleY?: number | undefined; 75 | scale?: number | undefined; 76 | 77 | rotate?: 'N' | 'R' | 'L' | 'I' | undefined; 78 | 79 | paddingwidth?: number | undefined; 80 | paddingheight?: number | undefined; 81 | 82 | paddingleft?: number | undefined; 83 | paddingright?: number | undefined; 84 | paddingtop?: number | undefined; 85 | paddingbottom?: number | undefined; 86 | 87 | monochrome?: boolean | undefined; 88 | sizelimit?: number | undefined; 89 | } 90 | export interface RawOptions extends BwippOptions { 91 | bcid: string; 92 | text: string; 93 | } 94 | export interface DrawingContext { 95 | setopts?(options: RenderOptions): void; 96 | scale(sx: number, sy: number): [number, number] | null; 97 | measure( 98 | str: string, 99 | font: string, 100 | fwidth: number, 101 | fheight: number, 102 | ): { width: number; ascent: number; descent: number }; 103 | init(width: number, height: number): void; 104 | line(x0: number, y0: number, x1: number, y1: number, lw: number, rgb: string): void; 105 | polygon(pts: Array<[number, number]>): void; 106 | hexagon(pts: [[number, number], [number, number], [number, number], [number, number], [number, number]]): void; 107 | ellipse(x: number, y: number, rx: number, ry: number, ccw: boolean): void; 108 | fill(rgb: string): void; 109 | text( 110 | x: number, 111 | y: number, 112 | str: string, 113 | rgb: string, 114 | font: { name: string; width: number; height: number; dx: number }, 115 | ): void; 116 | end(): T; 117 | } 118 | export function render(params: RenderOptions, drawing: DrawingContext): T; 119 | export function raw( 120 | options: RawOptions, 121 | ): 122 | | Array<{ bbs: number[]; bhs: number[]; sbs: number[] }> 123 | | Array<{ pixs: number[]; pixx: number; pixy: number; height: number; width: number }>; 124 | export function raw( 125 | bcid: string, 126 | text: string, 127 | opts: string, 128 | ): 129 | | Array<{ bbs: number[]; bhs: number[]; sbs: number[] }> 130 | | Array<{ pixs: number[]; pixx: number; pixy: number; height: number; width: number }>; 131 | export function raw( 132 | bcid: string, 133 | text: string, 134 | opts?: BwippOptions, 135 | ): 136 | | Array<{ bbs: number[]; bhs: number[]; sbs: number[] }> 137 | | Array<{ pixs: number[]; pixx: number; pixy: number; height: number; width: number }>; 138 | 139 | export const BWIPP_VERSION: string; 140 | export const BWIPJS_VERSION: string; 141 | 142 | // wrapper around FontLib.loadFont() 143 | export function loadFont(name: string, data: string | Uint8Array): void; 144 | export function loadFont(name: string, mult: number, data: string | Uint8Array): void; 145 | export function loadFont(name: string, multy: number, multx: number, data: string | Uint8Array): void; 146 | 147 | export namespace FontLib { 148 | export interface PathData 149 | extends Array< 150 | | { type: 'M'; x: number; y: number } 151 | | { type: 'L'; x: number; y: number } 152 | | { type: 'Q'; x: number; y: number; cx: number; cy: number } 153 | | { type: 'C'; x: number; y: number; cx1: number; cy1: number; cx2: number; cy2: number } 154 | > { 155 | ascent: number; 156 | descent: number; 157 | advance: number; 158 | } 159 | export function lookup(font: string): number; 160 | export function monochrome(mono: boolean): void; 161 | export function getglyph( 162 | fontid: number, 163 | charcode: number, 164 | width: number, 165 | height: number, 166 | ): { 167 | glyph: number; 168 | top: number; 169 | left: number; 170 | width: number; 171 | height: number; 172 | advance: number; 173 | pixels: Uint8Array; 174 | bytes: Uint8Array; 175 | cachekey: string; 176 | offset: number; 177 | }; 178 | export function getpaths(fontid: number, charcode: number, width: number, height: number): PathData; 179 | export function loadFont(name: string, data: string | Uint8Array): void; 180 | export function loadFont(name: string, mult: number, data: string | Uint8Array): void; 181 | export function loadFont(name: string, multy: number, multx: number, data: string | Uint8Array): void; 182 | } 183 | export function toSVG(opts: RenderOptions): string; 184 | export function drawingSVG(): DrawingContext; 185 | 186 | // platform-specific exports 187 | } 188 | 189 | export = BwipJs 190 | -------------------------------------------------------------------------------- /src/bwipjs.js: -------------------------------------------------------------------------------- 1 | // file : bwipjs.js 2 | // 3 | // Graphics-context interface to the BWIPP cross-compiled code 4 | 5 | var BWIPJS = (function() { 6 | 7 | // Math.floor(), etc. are notoriously slow. Caching seems to help. 8 | var floor = Math.floor; 9 | var round = Math.round; 10 | var ceil = Math.ceil; 11 | var min = Math.min; 12 | var max = Math.max; 13 | 14 | function BWIPJS(drawing) { 15 | if (this.constructor !== BWIPJS) { 16 | return new BWIPJS(drawing); 17 | } 18 | this.gstk = []; // Graphics save/restore stack 19 | this.cmds = []; // Graphics primitives to replay when rendering 20 | this.drawing = drawing; // Drawing interface 21 | 22 | this.reset(); 23 | 24 | // Drawing surface bounding box 25 | this.minx = this.miny = Infinity; 26 | this.maxx = this.maxy = -Infinity; 27 | }; 28 | 29 | // All graphics state that must be saved/restored is given a prefix of g_ 30 | BWIPJS.prototype.reset = function() { 31 | // Current Transform Matrix - since we don't do rotation, we can fake 32 | // the matrix math 33 | this.g_tdx = 0; // CTM x-offset 34 | this.g_tdy = 0; // CTM y-offset 35 | this.g_tsx = 1; // CTM x-scale factor 36 | this.g_tsy = 1; // CTM y-scale factor 37 | 38 | this.g_posx = 0; // current x position 39 | this.g_posy = 0; // current y position 40 | this.g_penw = 1; // current line/pen width 41 | this.g_path = []; // current path 42 | this.g_font = null; // current font object 43 | this.g_rgb = [0,0,0]; // current color (black) 44 | this.g_clip = false; // clip region active 45 | }; 46 | BWIPJS.prototype.save = function() { 47 | // clone all g_ properties 48 | var ctx = {}; 49 | for (var id in this) { 50 | if (id.indexOf('g_') == 0) { 51 | ctx[id] = clone(this[id]); 52 | } 53 | } 54 | this.gstk.push(ctx); 55 | 56 | // Perform a deep clone of the graphics state properties 57 | function clone(v) { 58 | if (v instanceof Array) { 59 | var t = []; 60 | for (var i = 0; i < v.length; i++) 61 | t[i] = clone(v[i]); 62 | return t; 63 | } 64 | if (v instanceof Object) { 65 | var t = {}; 66 | for (var id in v) 67 | t[id] = clone(v[id]); 68 | return t; 69 | } 70 | return v; 71 | } 72 | }; 73 | BWIPJS.prototype.restore = function() { 74 | if (!this.gstk.length) { 75 | throw new Error('grestore: stack underflow'); 76 | } 77 | var ctx = this.gstk.pop(); 78 | var self = this; 79 | if (this.g_clip && !ctx.g_clip) { 80 | this.cmds.push(function() { 81 | self.drawing.unclip(); 82 | }); 83 | } 84 | for (var id in ctx) { 85 | this[id] = ctx[id]; 86 | } 87 | }; 88 | // Per the postscript spec: 89 | // As discussed in Section 4.4.1, Current Path, points entered into a path 90 | // are immediately converted to device coordinates by the current 91 | // transformation matrix (CTM); subsequent modifications to the CTM do not 92 | // affect existing points. `currentpoint` computes the user space 93 | // coordinates corresponding to the current point according to the current 94 | // value of the CTM. Thus, if a current point is set and then the CTM is 95 | // changed, the coordinates returned by currentpoint will be different 96 | // from those that were originally specified for the point. 97 | BWIPJS.prototype.currpos = function() { 98 | return { x:(this.g_posx-this.g_tdx)/this.g_tsx, 99 | y:(this.g_posy-this.g_tdy)/this.g_tsy 100 | }; 101 | }; 102 | BWIPJS.prototype.currfont = function() { 103 | return this.g_font; 104 | }; 105 | BWIPJS.prototype.translate = function(x, y) { 106 | this.g_tdx = this.g_tsx * x; 107 | this.g_tdy = this.g_tsy * y; 108 | }; 109 | BWIPJS.prototype.scale = function(x, y) { 110 | this.g_tsx *= x; 111 | this.g_tsy *= y; 112 | var sxy = this.drawing.scale(this.g_tsx, this.g_tsy); 113 | if (sxy && sxy[0] && sxy[1]) { 114 | this.g_tsx = sxy[0]; 115 | this.g_tsy = sxy[1]; 116 | } 117 | }; 118 | BWIPJS.prototype.setlinewidth = function(w) { 119 | this.g_penw = w; 120 | }; 121 | BWIPJS.prototype.selectfont = function(f, z) { 122 | this.g_font = { FontName:this.jsstring(f), FontSize:+z }; 123 | }; 124 | BWIPJS.prototype.getfont = function() { 125 | return this.g_font.FontName; 126 | }; 127 | // Special function for converting a Uint8Array string to string. 128 | BWIPJS.prototype.jsstring = function(s) { 129 | if (s instanceof Uint8Array) { 130 | // Postscript (like C) treats nul-char as end of string. 131 | //for (var i = 0, l = s.length; i < l && s[i]; i++); 132 | //if (i < l) { 133 | // return String.fromCharCode.apply(null,s.subarray(0, i)); 134 | //} 135 | return String.fromCharCode.apply(null,s) 136 | } 137 | return ''+s; 138 | }; 139 | // Special function to replace setanycolor in BWIPP. 140 | // Converts a string of hex digits either rgb, rrggbb or ccmmyykk. 141 | // Or CSS-style #rgb and #rrggbb. 142 | BWIPJS.prototype.setcolor = function(s) { 143 | if (s instanceof Uint8Array) { 144 | s = this.jsstring(s); 145 | } 146 | if (!s) { 147 | return; 148 | } 149 | if (!/^(?:#?[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?|[0-9a-fA-F]{8})$/.test(s)) { 150 | throw new Error('bwip-js: invalid color: ' + s); 151 | } 152 | if (s[0] == '#') { 153 | s = s.substr(1); 154 | } 155 | if (s.length == 3) { 156 | var r = parseInt(s[0], 16); 157 | var g = parseInt(s[1], 16); 158 | var b = parseInt(s[2], 16); 159 | this.g_rgb = [ r<<4|r, g<<4|g, b<<4|b ]; 160 | } else if (s.length == 6) { 161 | var r = parseInt(s.substr(0,2), 16); 162 | var g = parseInt(s.substr(2,2), 16); 163 | var b = parseInt(s.substr(4,2), 16); 164 | this.g_rgb = [ r, g, b ]; 165 | } else { 166 | var c = parseInt(s.substr(0,2), 16) / 255; 167 | var m = parseInt(s.substr(2,2), 16) / 255; 168 | var y = parseInt(s.substr(4,2), 16) / 255; 169 | var k = parseInt(s.substr(6,2), 16) / 255; 170 | var r = round((1-c) * (1-k) * 255); 171 | var g = round((1-m) * (1-k) * 255); 172 | var b = round((1-y) * (1-k) * 255); 173 | this.g_rgb = [ r, g, b ]; 174 | } 175 | }; 176 | // Used only by swissqrcode 177 | BWIPJS.prototype.setrgbcolor = function(r,g,b) { 178 | this.g_rgb = [ r, g, b ]; 179 | }; 180 | // Returns the current rgb values as a 'RRGGBB' 181 | BWIPJS.prototype.getRGB = function() { 182 | var r = this.g_rgb[0].toString(16); 183 | var g = this.g_rgb[1].toString(16); 184 | var b = this.g_rgb[2].toString(16); 185 | return '00'.substr(r.length) + r + '00'.substr(g.length) + g + '00'.substr(b.length) + b; 186 | }; 187 | BWIPJS.prototype.newpath = function() { 188 | this.g_path = []; 189 | }; 190 | BWIPJS.prototype.closepath = function() { 191 | var path = this.g_path; 192 | var plen = path.length; 193 | if (!plen) return; 194 | 195 | var f = plen-1; 196 | for ( ; f >= 0 && path[f].op == 'l'; f--); 197 | f++; 198 | if (f < plen-1) { 199 | var poly = []; 200 | var xmin = Infinity; 201 | var ymin = Infinity; 202 | var xmax = -Infinity; 203 | var ymax = -Infinity; 204 | for (var i = f; i < plen; i++) { 205 | var a = path[i]; 206 | poly.push([ a.x0, a.y0 ]); 207 | if (xmin > a.x0) xmin = a.x0; 208 | if (xmax < a.x0) xmax = a.x0; 209 | if (ymin > a.y0) ymin = a.y0; 210 | if (ymax < a.y0) ymax = a.y0; 211 | } 212 | var a = path[plen-1]; 213 | var b = path[f]; 214 | if (a.x1 != b.x0 || a.y1 != b.y0) { 215 | poly.push([ a.x1, a.y1 ]); 216 | if (xmin > a.x1) xmin = a.x1; 217 | if (xmax < a.x1) xmax = a.x1; 218 | if (ymin > a.y1) ymin = a.y1; 219 | if (ymax < a.y1) ymax = a.y1; 220 | } 221 | path.splice(f, plen-f, 222 | { op:'p', x0:xmin, y0:ymin, x1:xmax, y1:ymax, poly:poly }); 223 | } else { 224 | path.push({ op:'c' }); 225 | } 226 | }; 227 | BWIPJS.prototype.moveto = function(x,y) { 228 | this.g_posx = this.g_tdx + this.g_tsx * x; 229 | this.g_posy = this.g_tdy + this.g_tsy * y; 230 | }; 231 | BWIPJS.prototype.rmoveto = function(x,y) { 232 | this.g_posx += this.g_tsx * x; 233 | this.g_posy += this.g_tsy * y; 234 | }; 235 | BWIPJS.prototype.lineto = function(x,y) { 236 | var x0 = round(this.g_posx); 237 | var y0 = round(this.g_posy); 238 | this.g_posx = this.g_tdx + this.g_tsx * x; 239 | this.g_posy = this.g_tdy + this.g_tsy * y; 240 | var x1 = round(this.g_posx); 241 | var y1 = round(this.g_posy); 242 | 243 | this.g_path.push({ op:'l', x0:x0, y0:y0, x1:x1, y1:y1 }); 244 | }; 245 | BWIPJS.prototype.rlineto = function(x,y) { 246 | var x0 = round(this.g_posx); 247 | var y0 = round(this.g_posy); 248 | this.g_posx += this.g_tsx * x; 249 | this.g_posy += this.g_tsy * y; 250 | var x1 = round(this.g_posx); 251 | var y1 = round(this.g_posy); 252 | 253 | this.g_path.push({ op:'l', x0:x0, y0:y0, x1:x1, y1:y1 }); 254 | }; 255 | // implements both arc and arcn 256 | BWIPJS.prototype.arc = function(x,y,r,sa,ea,ccw) { 257 | if (sa == ea) { 258 | return; 259 | } 260 | // For now, we only implement full circles... 261 | if (sa != 0 && sa != 360 || ea != 0 && ea != 360) { 262 | throw new Error('arc: not a full circle (' + sa + ',' + ea + ')'); 263 | } 264 | 265 | x = this.g_tdx + this.g_tsx * x; 266 | y = this.g_tdy + this.g_tsy * y; 267 | 268 | // e == ellipse 269 | var rx = r * this.g_tsx; 270 | var ry = r * this.g_tsy; 271 | this.g_path.push({ op:'e', x0:x-rx, y0:y-ry, x1:x+rx, y1:y+ry, 272 | x:x, y:y, rx:rx, ry:ry, sa:sa, ea:ea, ccw:ccw }); 273 | }; 274 | BWIPJS.prototype.stringwidth = function(str) { 275 | var tsx = this.g_tsx; 276 | var tsy = this.g_tsy; 277 | var size = +this.g_font.FontSize || 10; 278 | 279 | // The string can be either a uint8-string or regular string 280 | str = this.toUCS2(this.jsstring(str)); 281 | 282 | var bbox = this.drawing.measure(str, this.g_font.FontName, size*tsx, size*tsy); 283 | 284 | return { w:bbox.width/tsx, h:(bbox.ascent+bbox.descent)/tsy, 285 | a:bbox.ascent/tsy, d:bbox.descent/tsy }; 286 | }; 287 | BWIPJS.prototype.charpath = function(str, b) { 288 | var sw = this.stringwidth(str); 289 | 290 | // Emulate the char-path by placing a rectangle around it 291 | this.rlineto(0, sw.a); 292 | this.rlineto(sw.w, 0); 293 | this.rlineto(0, -sw.h); 294 | }; 295 | BWIPJS.prototype.pathbbox = function() { 296 | if (!this.g_path.length) throw new Error('pathbbox: --nocurrentpoint--'); 297 | var path = this.g_path; 298 | var llx = Infinity; 299 | var lly = Infinity; 300 | var urx = -Infinity; 301 | var ury = -Infinity; 302 | for (var i = 0; i < path.length; i++) { 303 | var a = path[i]; 304 | if (a.op == 'c') { 305 | continue; 306 | } 307 | if (a.x0 < a.x1) { 308 | if (llx > a.x0) llx = a.x0; 309 | if (urx < a.x1) urx = a.x1; 310 | } else { 311 | if (llx > a.x1) llx = a.x1; 312 | if (urx < a.x0) urx = a.x0; 313 | } 314 | if (a.y0 < a.y1) { 315 | if (lly > a.y0) lly = a.y0; 316 | if (ury < a.y1) ury = a.y1; 317 | } else { 318 | if (lly > a.y1) lly = a.y1; 319 | if (ury < a.y0) ury = a.y0; 320 | } 321 | } 322 | 323 | // Convert to user-space coordinates 324 | var rv = { llx:(llx-this.g_tdx)/this.g_tsx, 325 | lly:(lly-this.g_tdy)/this.g_tsy, 326 | urx:(urx-this.g_tdx)/this.g_tsx, 327 | ury:(ury-this.g_tdy)/this.g_tsy }; 328 | return rv; 329 | }; 330 | // Tranforms the pts array to standard (not y-inverted), unscalled values. 331 | BWIPJS.prototype.transform = function(pts) { 332 | var minx = this.minx; 333 | var maxy = this.maxy; 334 | 335 | for (var i = 0; i < pts.length; i++) { 336 | var pt = pts[i]; 337 | pt[0] = pt[0] - minx; 338 | pt[1] = maxy - pt[1]; 339 | } 340 | }; 341 | BWIPJS.prototype.stroke = function() { 342 | var tsx = this.g_tsx; 343 | var tsy = this.g_tsy; 344 | var path = this.g_path; 345 | var rgb = this.getRGB(); 346 | this.g_path = []; 347 | 348 | // This is a "super majority" round i.e. if over .66 round up. 349 | var penw = floor(this.g_penw * tsx + 0.66); 350 | var penh = floor(this.g_penw * tsy + 0.66); 351 | 352 | // Calculate the bounding boxes 353 | var nlines = 0, npolys = 0; 354 | for (var i = 0; i < path.length; i++) { 355 | var a = path[i]; 356 | if (a.op == 'l') { 357 | // We only stroke vertical and horizontal lines. Complex shapes are 358 | // always filled. 359 | if (a.x0 != a.x1 && a.y0 != a.y1) { 360 | throw new Error('stroke: --not-orthogonal--'); 361 | } 362 | var x0 = a.x0; 363 | var y0 = a.y0; 364 | var x1 = a.x1; 365 | var y1 = a.y1; 366 | 367 | // Half widths (may be factional) 368 | var penw2 = penw/2; 369 | var penh2 = penh/2; 370 | 371 | if (x0 > x1) { var t = x0; x0 = x1; x1 = t; } 372 | if (y0 > y1) { var t = y0; y0 = y1; y1 = t; } 373 | if (x0 == x1) { 374 | this.bbox(x0-penw2, y0, x0+penw-penw2-1, y1); // vertical line 375 | } else { 376 | this.bbox(x0, y0-penh+penh2+1, x1, y1+penh2); // horizontal line 377 | } 378 | nlines++; 379 | } else if (a.op == 'p') { 380 | // Closed (rectangular) poly (border around the barcode) 381 | var minx = Infinity; 382 | var miny = Infinity; 383 | var maxx = -Infinity; 384 | var maxy = -Infinity; 385 | var pts = a.poly; 386 | if (pts.length != 4) { 387 | throw new Error('stroke: --not-a-rect--'); 388 | } 389 | for (var i = 0, j = pts.length-1; i < pts.length; j = i++) { 390 | var xj = pts[j][0]; 391 | var yj = pts[j][1]; 392 | var xi = pts[i][0]; 393 | var yi = pts[i][1]; 394 | 395 | if (xi != xj && yi != yj) { 396 | throw new Error('stroke: --not-orthogonal--'); 397 | } 398 | 399 | if (xi < minx) minx = xi; 400 | if (xi > maxx) maxx = xi; 401 | if (yi < miny) miny = yi; 402 | if (yi > maxy) maxy = yi; 403 | } 404 | 405 | // Half widths (integer) 406 | var penw2 = ceil(penw/2); 407 | var penh2 = ceil(penh/2); 408 | 409 | // We render these as two polygons plus a fill. 410 | // When border width is odd, allocate the bigger half to the outside. 411 | this.bbox(minx-penw2, miny-penh2, maxx+penw2, maxy+penh2); 412 | npolys++; 413 | } else { 414 | throw new Error('stroke: --not-a-line--'); 415 | } 416 | } 417 | 418 | // Draw the lines 419 | var self = this; 420 | this.cmds.push(function() { 421 | // Half widths (big half and remaining half) 422 | var bigw2 = ceil(penw/2); 423 | var bigh2 = ceil(penh/2); 424 | var remw2 = penw - bigw2; 425 | var remh2 = penh - bigh2; 426 | 427 | for (var i = 0; i < path.length; i++) { 428 | var a = path[i] 429 | if (a.op == 'l') { 430 | var pts = [ [ a.x0, a.y0 ], [ a.x1, a.y1 ] ]; 431 | self.transform(pts); 432 | self.drawing.line(pts[0][0], pts[0][1], pts[1][0], pts[1][1], 433 | a.x0 == a.x1 ? penw : penh, rgb); 434 | self.fill(rgb); 435 | } else { 436 | var pts = a.poly; 437 | self.transform(pts); 438 | var x0 = min(pts[0][0], pts[2][0]); 439 | var x1 = max(pts[0][0], pts[2][0]); 440 | var y0 = min(pts[0][1], pts[2][1]); 441 | var y1 = max(pts[0][1], pts[2][1]); 442 | 443 | // Top and left edges are "inside" the polygon. 444 | // Bottom and right edges are outside. 445 | 446 | // outside, counter-clockwise 447 | self.drawing.polygon([ 448 | [ x0-bigw2, y0-bigh2 ], 449 | [ x0-bigw2, y1+bigh2+1 ], 450 | [ x1+bigw2+1, y1+bigh2+1 ], 451 | [ x1+bigw2+1, y0-bigh2 ], 452 | ]); 453 | // inside, clockwise 454 | self.drawing.polygon([ 455 | [ x0+remw2, y0+remh2 ], 456 | [ x1-remw2+1, y0+remh2 ], 457 | [ x1-remw2+1, y1-remh2+1 ], 458 | [ x0+remw2, y1-remh2+1 ], 459 | ]); 460 | self.drawing.fill(rgb); 461 | } 462 | } 463 | }); 464 | }; 465 | BWIPJS.prototype.fill = function() { 466 | var path = this.g_path; 467 | var rgb = this.getRGB(); 468 | this.g_path = []; 469 | 470 | // Calculate the bounding boxes 471 | for (var p = 0; p < path.length; p++) { 472 | var a = path[p]; 473 | if (a.op == 'p') { // polygon 474 | var minx = Infinity; 475 | var miny = Infinity; 476 | var maxx = -Infinity; 477 | var maxy = -Infinity; 478 | var pts = a.poly; 479 | for (var i = 0; i < pts.length; i++) { 480 | var xi = pts[i][0]; 481 | var yi = pts[i][1]; 482 | 483 | if (xi < minx) minx = xi; 484 | if (xi > maxx) maxx = xi; 485 | if (yi < miny) miny = yi; 486 | if (yi > maxy) maxy = yi; 487 | } 488 | // With polygons, the right and bottom edges are "outside" and do not 489 | // contribute to the bounding box. But we are in postscript inverted-y 490 | // mode. 491 | this.bbox(minx, miny+1, maxx-1, maxy); 492 | } else if (a.op == 'e') { // ellipse 493 | this.bbox(a.x - a.rx, a.y - a.ry, a.x + a.rx, a.y + a.ry); 494 | } else { 495 | throw new Error('fill: --not-a-polygon--'); 496 | } 497 | } 498 | 499 | // Render the poly 500 | var self = this; 501 | this.cmds.push(function() { 502 | for (var i = 0; i < path.length; i++) { 503 | var a = path[i]; 504 | if (a.op == 'p') { 505 | var pts = a.poly 506 | self.transform(pts); 507 | self.drawing.polygon(pts); 508 | } else if (a.op == 'e') { 509 | var pts = [ [ a.x, a.y ] ]; 510 | self.transform(pts); 511 | self.drawing.ellipse(pts[0][0], pts[0][1], a.rx, a.ry, a.ccw); 512 | } 513 | } 514 | self.drawing.fill(rgb); 515 | }); 516 | }; 517 | BWIPJS.prototype.clip = function() { 518 | var path = this.g_path; 519 | this.g_path = []; 520 | this.g_clip = true; 521 | 522 | var self = this; 523 | this.cmds.push(function() { 524 | var polys = []; 525 | for (var i = 0; i < path.length; i++) { 526 | var a = path[i]; 527 | if (a.op == 'p') { 528 | var pts = a.poly 529 | self.transform(pts); 530 | polys.push(pts); 531 | } else { 532 | throw new Error('clip: only polygon regions supported'); 533 | } 534 | } 535 | self.drawing.clip(polys); 536 | }); 537 | }; 538 | 539 | // The pix array is in standard (not y-inverted postscript) orientation. 540 | BWIPJS.prototype.showmaxicode = function(pix) { 541 | var tsx = this.g_tsx; 542 | var tsy = this.g_tsy; 543 | var rgb = this.getRGB(); 544 | 545 | // Module width. Module height is an integer multiple of tsy. 546 | var twidth = 1.04 * tsx * 100; 547 | var mwidth = (twidth / 30)|0; 548 | if (twidth - (mwidth*30-1) > 9) { 549 | mwidth++; 550 | } 551 | 552 | // Dimensions needed for plotting the hexagons. These must be integer values. 553 | var w, h, wgap, hgap; 554 | // if (opts.??? ) { 555 | // // Create a one or two pixel gap 556 | // wgap = (mwidth & 1) ? 1 : 2; 557 | // hgap = 1; 558 | // w = mwidth - gap; 559 | // h = 4 * tsy; 560 | // } else { 561 | // Create a 1/8mm gap 562 | wgap = (tsx/2)|0; 563 | hgap = (tsy/2)|0; 564 | w = mwidth - wgap; 565 | if (w & 1) { 566 | w--; 567 | } 568 | h = ((4*tsy)|0) - hgap; 569 | //} 570 | 571 | // These must be integer values 572 | var w2 = w / 2 - 1; // half width 573 | var qh = ((w2+1) / 2)|0; // quarter height 574 | var vh = h - 2 - 2 * qh; // side height 575 | 576 | // Bounding box 577 | this.bbox(0, 0, mwidth*30 - wgap, tsy * 3 * 32 + tsy * 4 - hgap); 578 | 579 | // Render the elements 580 | var self = this; 581 | this.cmds.push(function() { 582 | // Draw the hexagons 583 | for (var i = 0; i < pix.length; i++) { 584 | var c = pix[i]; 585 | var x = c % 30; 586 | var y = (c / 30)|0; 587 | 588 | // Adjust x,y to the top of hexagon 589 | x *= mwidth; 590 | x += (y & 1) ? mwidth : mwidth/2; 591 | x = x|0; 592 | 593 | y = 33 - y; // invert for postscript notation 594 | y *= tsy * 3; 595 | y += tsy * 2 - h/2; 596 | y = y|0; 597 | 598 | // Build bottom up so the drawing is top-down. 599 | var pts = [ [ x-0.5, y-- ] ]; 600 | y -= qh-1; 601 | pts.push([x-1-w2, y--]); 602 | y -= vh; 603 | pts.push([x-1-w2, y--]); 604 | y -= qh-1; 605 | pts.push([x-0.5, y++]); 606 | y += qh-1; 607 | pts.push([x+w2, y++]); 608 | y += vh; 609 | pts.push([x+w2, y++]); 610 | 611 | self.transform(pts); 612 | self.drawing.hexagon(pts, rgb); 613 | } 614 | self.drawing.fill(rgb); 615 | 616 | 617 | // Draw the rings 618 | var x = (14 * mwidth + mwidth/2 + 0.01)|0; 619 | var y = ((12 * 4 + 3) * tsy - qh/2 + 0.01)|0; 620 | self.drawing.ellipse(x, y, (0.5774*3.5*tsx+0.01)|0, (0.5774*3.5*tsy+0.01)|0, true); 621 | self.drawing.ellipse(x, y, (1.3359*3.5*tsx+0.01)|0, (1.3359*3.5*tsy+0.01)|0, false); 622 | self.drawing.fill(rgb); 623 | self.drawing.ellipse(x, y, (2.1058*3.5*tsx+0.01)|0, (2.1058*3.5*tsy+0.01)|0, true); 624 | self.drawing.ellipse(x, y, (2.8644*3.5*tsx+0.01)|0, (2.8644*3.5*tsy+0.01)|0, false); 625 | self.drawing.fill(rgb); 626 | self.drawing.ellipse(x, y, (3.6229*3.5*tsx+0.01)|0, (3.6229*3.5*tsy+0.01)|0, true); 627 | self.drawing.ellipse(x, y, (4.3814*3.5*tsx+0.01)|0, (4.3814*3.5*tsy+0.01)|0, false); 628 | self.drawing.fill(rgb); 629 | 630 | }); 631 | }; 632 | // UTF-8 to UCS-2 (no surrogates) 633 | BWIPJS.prototype.toUCS2 = function(str) { 634 | return str.replace(/[\xc0-\xdf][\x80-\xbf]|[\xe0-\xff][\x80-\xbf]{2}/g, 635 | function(s) { 636 | var code; 637 | if (s.length == 2) { 638 | code = ((s.charCodeAt(0)&0x1f)<<6)| 639 | (s.charCodeAt(1)&0x3f); 640 | } else { 641 | code = ((s.charCodeAt(0)&0x0f)<<12)| 642 | ((s.charCodeAt(1)&0x3f)<<6)| 643 | (s.charCodeAt(2)&0x3f); 644 | } 645 | return String.fromCharCode(code); 646 | }); 647 | }; 648 | // dx,dy are inter-character gaps 649 | BWIPJS.prototype.show = function(str, dx, dy) { 650 | if (!str.length) { 651 | return; 652 | } 653 | 654 | // Capture current graphics state 655 | var tsx = this.g_tsx; 656 | var tsy = this.g_tsy; 657 | var name = this.g_font.FontName || 'OCR-B'; 658 | var size = (this.g_font.FontSize || 10); 659 | var szx = size * tsx; 660 | var szy = size * tsy; 661 | var posx = this.g_posx; 662 | var posy = this.g_posy; 663 | var rgb = this.getRGB(); 664 | 665 | // The string can be either a uint8-string or regular string. 666 | str = this.toUCS2(this.jsstring(str)); 667 | 668 | // Convert dx,dy to device space 669 | dx = tsx * dx || 0; 670 | dy = tsy * dy || 0; 671 | 672 | // Bounding box. 673 | var base = posy + dy; 674 | var bbox = this.drawing.measure(str, name, szx, szy); 675 | var width = bbox.width + (str.length-1) * dx; 676 | this.bbox(posx, base-bbox.descent+1, posx+width-1, base+bbox.ascent); 677 | this.g_posx += width; 678 | 679 | var self = this; 680 | self.cmds.push(function() { 681 | // self.transform() 682 | var x = posx - self.minx; 683 | var y = self.maxy - posy; 684 | self.drawing.text(x, y, str, rgb, { name:name, width:szx, height:szy, dx:dx }); 685 | }); 686 | }; 687 | // drawing surface bounding box 688 | BWIPJS.prototype.bbox = function(x0, y0, x1, y1) { 689 | if (x0 > x1) { var t = x0; x0 = x1; x1 = t; } 690 | if (y0 > y1) { var t = y0; y0 = y1; y1 = t; } 691 | 692 | x0 = floor(x0); 693 | y0 = floor(y0); 694 | x1 = ceil(x1); 695 | y1 = ceil(y1); 696 | 697 | if (this.minx > x0) this.minx = x0; 698 | if (this.maxx < x1) this.maxx = x1; 699 | if (this.miny > y0) this.miny = y0; 700 | if (this.maxy < y1) this.maxy = y1; 701 | }; 702 | BWIPJS.prototype.render = function() { 703 | if (this.minx === Infinity) { 704 | // Most likely, `dontdraw` was set in the options 705 | return false; 706 | } 707 | // Draw the image 708 | this.drawing.init(this.maxx - this.minx + 1, this.maxy - this.miny + 1, 709 | this.g_tsx, this.g_tsy); 710 | for (var i = 0, l = this.cmds.length; i < l; i++) { 711 | this.cmds[i](); 712 | } 713 | return this.drawing.end(); 714 | }; 715 | 716 | return BWIPJS; 717 | })(); // BWIPJS closure 718 | -------------------------------------------------------------------------------- /src/drawing-builtin.js: -------------------------------------------------------------------------------- 1 | // drawing-builtin.js 2 | // 3 | // The aliased (except the fonts) graphics used by drawing-canvas.js and 4 | // drawing-zlibpng.js 5 | // 6 | // All x,y and lengths are integer values. 7 | // 8 | // For the methods that take a color `rgb` parameter, the value is always a 9 | // string with format RRGGBB. 10 | function DrawingBuiltin() { 11 | var floor = Math.floor; 12 | 13 | // Unrolled x,y rotate/translate matrix 14 | var tx0 = 0, tx1 = 0, tx2 = 0, tx3 = 0; 15 | var ty0 = 0, ty1 = 0, ty2 = 0, ty3 = 0; 16 | 17 | var opts; // see setopts() 18 | var gs_image, gs_rowbyte; // rowbyte will be 1 for png's, 0 for canvas 19 | var gs_width, gs_height; // image size, in pixels 20 | var gs_dx, gs_dy; // x,y translate (padding) 21 | var gs_r, gs_g, gs_b; // rgb 22 | var gs_xymap; // edge map 23 | var gs_xyclip; // clip region map (similar to xymap) 24 | 25 | return { 26 | // setopts() is called after the options are fixed-up/normalized, 27 | // but before calling into BWIPP. 28 | // This method allows omitting the options in the constructor call. 29 | // The method is optional. 30 | setopts(options) { 31 | opts = options; 32 | }, 33 | 34 | // Ensure compliant bar codes by always using integer scaling factors. 35 | scale : function(sx, sy) { 36 | // swissqrcode requires clipping and drawing that are not scaled to the 37 | // the barcode module size. 38 | if (opts.bcid == 'swissqrcode') { 39 | return [ sx, sy ]; 40 | } else { 41 | return [ (sx|0)||1, (sy|0)||1 ]; 42 | } 43 | }, 44 | 45 | // Measure text. This and scale() are the only drawing primitives that 46 | // are called before init(). 47 | // 48 | // `font` is the font name typically OCR-A or OCR-B. 49 | // `fwidth` and `fheight` are the requested font cell size. They will 50 | // usually be the same, except when the scaling is not symetric. 51 | measure : function(str, font, fwidth, fheight) { 52 | fwidth = fwidth|0; 53 | fheight = fheight|0; 54 | 55 | var fontid = FontLib.lookup(font); 56 | var width = 0; 57 | var ascent = 0; 58 | var descent = 0; 59 | for (var i = 0, l = str.length; i < l; i++) { 60 | var ch = str.charCodeAt(i); 61 | var glyph = FontLib.getglyph(fontid, ch, fwidth, fheight); 62 | 63 | ascent = Math.max(ascent, glyph.top); 64 | descent = Math.max(descent, glyph.height - glyph.top); 65 | 66 | if (i == l-1) { 67 | width += glyph.left + glyph.width; 68 | } else { 69 | width += glyph.advance; 70 | } 71 | } 72 | return { width:width, ascent:ascent, descent:descent }; 73 | }, 74 | 75 | // width and height represent the maximum bounding box the graphics will occupy. 76 | // The dimensions are for an unrotated rendering. Adjust as necessary. 77 | init : function(width, height) { 78 | // Add in the effects of padding. These are always set before the 79 | // drawing constructor is called. 80 | var padl = opts.paddingleft; 81 | var padr = opts.paddingright; 82 | var padt = opts.paddingtop; 83 | var padb = opts.paddingbottom; 84 | var rot = opts.rotate || 'N'; 85 | 86 | width += padl + padr; 87 | height += padt + padb; 88 | 89 | if (+opts.sizelimit && +opts.sizelimit < width * height) { 90 | throw new Error('Image size over limit'); 91 | } 92 | 93 | // Transform indexes are: x, y, w, h 94 | switch (rot) { 95 | // tx = w-y, ty = x 96 | case 'R': tx1 = -1; tx2 = 1; ty0 = 1; break; 97 | // tx = w-x, ty = h-y 98 | case 'I': tx0 = -1; tx2 = 1; ty1 = -1; ty3 = 1; break; 99 | // tx = y, ty = h-x 100 | case 'L': tx1 = 1; ty0 = -1; ty3 = 1; break; 101 | // tx = x, ty = y 102 | default: tx0 = ty1 = 1; break; 103 | } 104 | 105 | // Setup the graphics state 106 | var swap = rot == 'L' || rot == 'R'; 107 | gs_width = swap ? height : width; 108 | gs_height = swap ? width : height; 109 | gs_dx = padl; 110 | gs_dy = padt; 111 | gs_xymap = []; 112 | gs_xymap.min = Infinity; 113 | gs_xyclip = null; 114 | gs_r = gs_g = gs_b = 0; 115 | 116 | // Get the rgba image from the constructor 117 | var res = this.image(gs_width, gs_height); 118 | gs_image = res.buffer; 119 | gs_rowbyte = res.ispng ? 1 : 0; 120 | }, 121 | // Unconnected stroked lines are used to draw the bars in linear barcodes; 122 | // and the border around a linear barcode (e.g. ITF-14) 123 | // No line cap should be applied. These lines are always orthogonal. 124 | line : function(x0, y0, x1, y1, lw, rgb) { 125 | x0 = x0|0; 126 | y0 = y0|0; 127 | x1 = x1|0; 128 | y1 = y1|0; 129 | 130 | // Most linear barcodes, the line width will be integral. The exceptions 131 | // are variable width barcodes (e.g. code39) and the postal 4-state codes. 132 | lw = Math.round(lw) || 1; 133 | 134 | if (y1 < y0) { var t = y0; y0 = y1; y1 = t; } 135 | if (x1 < x0) { var t = x0; x0 = x1; x1 = t; } 136 | 137 | gs_r = parseInt(rgb.substr(0,2), 16); 138 | gs_g = parseInt(rgb.substr(2,2), 16); 139 | gs_b = parseInt(rgb.substr(4,2), 16); 140 | 141 | // Horizontal or vertical line? 142 | var w2 = (lw/2)|0; 143 | if (x0 == x1) { 144 | // Vertical line 145 | x0 = x0 - lw + w2; // big half 146 | x1 = x1 + w2 - 1; // small half 147 | } else { 148 | // Horizontal line (inverted halves) 149 | y0 = y0 - w2; 150 | y1 = y1 + lw - w2 - 1; 151 | } 152 | for (var y = y0; y <= y1; y++) { 153 | for (var x = x0; x <= x1; x++) { 154 | set(x, y, 255); 155 | } 156 | } 157 | }, 158 | 159 | // Polygons are used to draw the connected regions in a 2d barcode. 160 | // These will always be unstroked, filled, orthogonal shapes. 161 | // 162 | // You will see a series of polygon() calls, followed by a fill(). 163 | polygon : function(pts) { 164 | var npts = pts.length; 165 | for (var j = npts-1, i = 0; i < npts; j = i++) { 166 | if (pts[j][0] == pts[i][0]) { 167 | // Vertical lines do not get their end points. End points 168 | // are added by the horizontal line logic. 169 | var xj = pts[j][0]|0; // i or j, doesn't matter 170 | var yj = pts[j][1]|0; 171 | var yi = pts[i][1]|0; 172 | if (yj > yi) { 173 | for (var y = yi+1; y < yj; y++) { 174 | addPoint(xj, y); 175 | } 176 | } else { 177 | for (var y = yj+1; y < yi; y++) { 178 | addPoint(xj, y); 179 | } 180 | } 181 | } else { 182 | var xj = pts[j][0]|0; 183 | var xi = pts[i][0]|0; 184 | var yj = pts[j][1]|0; // i or j, doesn't matter 185 | 186 | // Horizontal lines are tricky. As a rule, top lines get filled, 187 | // bottom lines do not (similar to how left edges get filled and 188 | // right edges do not). 189 | // 190 | // Where it gets complex is deciding whether the line actually 191 | // adds edges. There are cases where a horizontal line does 192 | // not add anything to the scanline plotting. And it doesn't 193 | // actually matter whether the line is a top or bottom edge, 194 | // the logic is the same. 195 | // 196 | // A left edge is added if the edge to its left is below. 197 | // A right edge is added if the edge to its right is below. 198 | if (xj < xi) { 199 | var yl = pts[j == 0 ? npts-1 : j-1][1]; // left edge 200 | var yr = pts[i == npts-1 ? 0 : i+1][1]; // right edge 201 | if (yl > yj) { 202 | addPoint(xj, yj); 203 | } 204 | if (yr > yj) { 205 | addPoint(xi, yj); 206 | } 207 | } else { 208 | var yl = pts[i == npts-1 ? 0 : i+1][1]; // left edge 209 | var yr = pts[j == 0 ? npts-1 : j-1][1]; // right edge 210 | if (yl > yj) { 211 | addPoint(xi, yj); 212 | } 213 | if (yr > yj) { 214 | addPoint(xj, yj); 215 | } 216 | } 217 | } 218 | } 219 | }, 220 | // An unstroked, filled hexagon used by maxicode. You can choose to fill 221 | // each individually, or wait for the final fill(). 222 | // 223 | // The hexagon is drawn from the top, counter-clockwise. 224 | // 225 | // The X-coordinate for the top and bottom points on the hexagon is always 226 | // .5 pixels. We draw our hexagons with a 2 pixel flat top. 227 | // 228 | // All other points of the polygon/hexagon are guaranteed to be integer values. 229 | hexagon : function(pts, rgb) { 230 | var x = pts[0][0]|0; 231 | var y = pts[0][1]|0; 232 | var qh = (pts[1][1] - pts[0][1])|0; // height of triangle (quarter height) 233 | var vh = (pts[2][1] - pts[1][1] - 1)|0; // height of vertical side 234 | var xl = (pts[2][0])|0; // left side 235 | var xr = (pts[4][0])|0; // right side 236 | 237 | gs_r = parseInt(rgb.substr(0,2), 16); 238 | gs_g = parseInt(rgb.substr(2,2), 16); 239 | gs_b = parseInt(rgb.substr(4,2), 16); 240 | 241 | fillSegment(x, x+1, y++); 242 | for (var k = 1; k < qh; k++) { 243 | fillSegment(x-2*k, x+1+2*k, y++); 244 | } 245 | for (var k = 0; k <= vh; k++) { 246 | fillSegment(xl, xr, y++); 247 | } 248 | for (var k = qh-1; k >= 1; k--) { 249 | fillSegment(x-2*k, x+1+2*k, y++); 250 | } 251 | fillSegment(x, x+1, y); 252 | }, 253 | // An unstroked, filled ellipse. Used by dotcode and maxicode at present. 254 | // maxicode issues pairs of ellipse calls (one cw, one ccw) followed by a fill() 255 | // to create the bullseye rings. dotcode issues all of its ellipses then a 256 | // fill(). 257 | ellipse : function(x, y, rx, ry, ccw) { 258 | drawEllipse((x-rx)|0, (y-ry)|0, (x+rx)|0, (y+ry)|0, ccw); 259 | }, 260 | // PostScript's default fill rule is non-zero but since there are never 261 | // intersecting regions, we use the easier to implement even-odd. 262 | fill : function(rgb) { 263 | gs_r = parseInt(rgb.substr(0,2), 16); 264 | gs_g = parseInt(rgb.substr(2,2), 16); 265 | gs_b = parseInt(rgb.substr(4,2), 16); 266 | 267 | evenodd(); 268 | gs_xymap = []; 269 | gs_xymap.min = Infinity; 270 | }, 271 | // Currently only used by swissqrcode. The `polys` area is an array of 272 | // arrays of points. Each array of points is identical to the `pts` 273 | // parameter passed to polygon(). The postscript default clipping rule, 274 | // like the fill rule, is even-odd winding. 275 | clip : function(polys) { 276 | if (!gs_xyclip) { 277 | gs_xyclip = []; 278 | gs_xyclip.min = Infinity; 279 | } 280 | // Swap out the xymap for the clip map so addPoint() works on it. 281 | var xymap = gs_xymap; 282 | gs_xymap = gs_xyclip; 283 | 284 | // Now just use the polygon() logic to fill in the clipping regions. 285 | for (var i = 0, l = polys.length; i < l; i++) { 286 | this.polygon(polys[i]); 287 | } 288 | 289 | // Restore 290 | gs_xymap = xymap; 291 | }, 292 | unclip : function() { 293 | gs_xyclip = null; 294 | }, 295 | // Draw text with optional inter-character spacing. `y` is the baseline. 296 | // font is an object with properties { name, width, height, dx } 297 | // width and height are the font cell size. 298 | // dx is extra space requested between characters (usually zero). 299 | text : function(x, y, str, rgb, font) { 300 | x = x|0; 301 | y = y|0; 302 | 303 | gs_r = parseInt(rgb.substr(0,2), 16); 304 | gs_g = parseInt(rgb.substr(2,2), 16); 305 | gs_b = parseInt(rgb.substr(4,2), 16); 306 | 307 | var fontid = FontLib.lookup(font.name); 308 | var fwidth = font.width|0; 309 | var fheight = font.height|0; 310 | var dx = font.dx|0; 311 | for (var k = 0; k < str.length; k++) { 312 | var ch = str.charCodeAt(k); 313 | var glyph = FontLib.getglyph(fontid, ch, fwidth, fheight); 314 | 315 | var gt = y - glyph.top; 316 | var gl = glyph.left; 317 | var gw = glyph.width; 318 | var gh = glyph.height; 319 | var gb = glyph.bytes; 320 | var go = glyph.offset; // offset into bytes 321 | 322 | for (var i = 0; i < gw; i++) { 323 | for (var j = 0; j < gh; j++) { 324 | var a = gb[go + j * gw + i]; 325 | if (a) { 326 | set(x+gl+i, gt+j, a); 327 | } 328 | } 329 | } 330 | x += glyph.advance + dx; 331 | } 332 | }, 333 | // Called after all drawing is complete. 334 | end : function() { 335 | }, 336 | }; 337 | 338 | // This code is specialized to deal with two types of RGBA buffers: 339 | // - canvas style, which is true RGBA 340 | // - PNG style, which has a one-byte "filter code" prefixing each row. 341 | function set(x, y, a) { 342 | if (gs_xyclip && clipped(x, y)) { 343 | return; 344 | } 345 | // translate/rotate 346 | x += gs_dx; 347 | y += gs_dy; 348 | var tx = tx0 * x + tx1 * y + tx2 * (gs_width-1) + tx3 * (gs_height-1); 349 | var ty = ty0 * x + ty1 * y + ty2 * (gs_width-1) + ty3 * (gs_height-1); 350 | 351 | // https://en.wikipedia.org/wiki/Alpha_compositing 352 | var offs = (ty * gs_width + tx) * 4 + (ty+1) * gs_rowbyte; 353 | var dsta = gs_image[offs+3] / 255; 354 | var srca = a / 255; 355 | var inva = (1 - srca) * dsta; 356 | var outa = srca + inva; 357 | 358 | gs_image[offs+0] = ((gs_r * srca + gs_image[offs+0] * inva) / outa)|0; 359 | gs_image[offs+1] = ((gs_g * srca + gs_image[offs+1] * inva) / outa)|0; 360 | gs_image[offs+2] = ((gs_b * srca + gs_image[offs+2] * inva) / outa)|0; 361 | gs_image[offs+3] = (255 * outa)|0; 362 | } 363 | 364 | // Add a point on an edge to the scanline map. 365 | function addPoint(x, y) { 366 | if (gs_xymap.min > y) gs_xymap.min = y; 367 | if (!gs_xymap[y]) { 368 | gs_xymap[y] = [ x ]; 369 | } else { 370 | gs_xymap[y].push(x); 371 | } 372 | } 373 | 374 | function fillSegment(x0, x1, y) { 375 | while (x0 <= x1) { 376 | set(x0++, y, 255); 377 | } 378 | } 379 | 380 | // even-odd fill 381 | // 382 | // This implementation is optimized for BWIPP's simple usage. 383 | // It is not a general purpose scanline fill. It relies heavily on 384 | // polygon() creating the correct intersections. 385 | function evenodd() { 386 | var ymin = gs_xymap.min; 387 | var ymax = gs_xymap.length-1; 388 | 389 | for (var y = ymin; y <= ymax; y++) { 390 | var pts = gs_xymap[y]; 391 | if (!pts) { 392 | continue 393 | } 394 | pts.sort(function(a, b) { return a - b; }); 395 | 396 | var wn = false; 397 | var xl = 0; 398 | for (var n = 0, npts = pts.length; n < npts; n++) { 399 | var x = pts[n]; 400 | if (wn) { 401 | fillSegment(xl, x-1, y); 402 | } else { 403 | xl = x; 404 | } 405 | wn = !wn; 406 | } 407 | } 408 | } 409 | 410 | function drawEllipse(x0, y0, x1, y1, dir) { 411 | x0 = x0|0; 412 | y0 = y0|0; 413 | x1 = x1|0; 414 | y1 = y1|0; 415 | 416 | var a = Math.abs(x1-x0); 417 | var b = Math.abs(y1-y0); 418 | var b1 = b & 1; 419 | var dx = 4*(1-a)*b*b; 420 | var dy = 4*(b1+1)*a*a; 421 | var err = dx + dy + b1*a*a; 422 | var e2; 423 | 424 | // Left and right edges 425 | var left = [], right = []; 426 | left.min = right.min = Infinity; 427 | 428 | if (x0 > x1) { x0 = x1; x1 += a; } 429 | if (y0 > y1) y0 = y1; 430 | y0 += ((b+1)/2)|0; 431 | y1 = y0 - b1; 432 | a *= 8*a; b1 = 8*b*b; 433 | 434 | do { 435 | maxedge(right, x1, y0); // 1st quadrant 436 | minedge(left, x0, y0); // 2nd quadrant 437 | minedge(left, x0, y1); // 3rd quadrant 438 | maxedge(right, x1, y1); // 4th quadrant 439 | e2 = 2*err; 440 | if (e2 >= dx) { x0++; x1--; dx += b1; err += dx; } 441 | if (e2 <= dy) { y0++; y1--; dy += a; err += dy; } 442 | } while (x0 <= x1); 443 | 444 | while (y0-y1 < b) { // too early stop of flat ellipse 445 | maxedge(right, x1+1, y0); 446 | minedge(left, x0-1, y0++); 447 | minedge(left, x0-1, y1); 448 | maxedge(right, x1+1, y1--); 449 | } 450 | 451 | for (var y = left.min, max = left.length-1; y <= max; y++) { 452 | addPoint(left[y], y); 453 | } 454 | // The points we calculated are "inside". The fill algorithm excludes 455 | // right edges, so +1 on each x. 456 | for (var y = right.min, max = right.length-1; y <= max; y++) { 457 | addPoint(right[y]+1, y); 458 | } 459 | 460 | function minedge(e, x, y) { 461 | if (e.min > y) e.min = y; 462 | var ey = e[y]; 463 | if (ey == null || ey > x) { 464 | e[y] = x; 465 | } 466 | } 467 | 468 | function maxedge(e, x, y) { 469 | if (e.min > y) e.min = y; 470 | var ey = e[y]; 471 | if (ey == null || ey < x) { 472 | e[y] = x; 473 | } 474 | } 475 | } 476 | 477 | // Returns true if outside the clipping region. 478 | function clipped(x, y) { 479 | var pts = gs_xyclip[y]; 480 | if (!pts) { 481 | return true; 482 | } 483 | if (!pts.sorted) { 484 | pts.sort(function(a, b) { return a - b; }); 485 | pts.sorted = true; 486 | } 487 | 488 | var wn = false; 489 | for (var n = 0, npts = pts.length; n < npts; n++) { 490 | var xn = pts[n]; 491 | if (xn > x) { 492 | return !wn; 493 | } else if (xn == x) { 494 | return wn; 495 | } 496 | wn = !wn; 497 | } 498 | return true; 499 | } 500 | 501 | // Returns 1 if clockwise, -1 if ccw. 502 | function polydir(pts) { 503 | var xp = 0; 504 | for (var i = 0, l = pts.length, j = l-1; i < l; j = i++) { 505 | xp += pts[j][0] * pts[i][1] - pts[i][0] * pts[j][1]; 506 | } 507 | return xp > 0 ? 1 : -1; 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /src/drawing-canvas.js: -------------------------------------------------------------------------------- 1 | // drawing-canvas.js 2 | // 3 | // `maybe` maybe the canvas, pre v4.0. 4 | function DrawingCanvas(canvas, maybe) { 5 | // Pre setops() backward compatibility 6 | if (maybe && maybe instanceof HTMLCanvasElement) { 7 | canvas = maybe; 8 | } 9 | 10 | var img; 11 | var ctx = canvas.getContext('2d', { willReadFrequently:true }); 12 | var drawing = DrawingBuiltin(); 13 | 14 | // Provide our specializations for the builtin drawing 15 | drawing.image = image; 16 | drawing.end = end; 17 | 18 | // Reflect setopts() into the super 19 | var opts; 20 | var _setopts = drawing.setopts; 21 | drawing.setopts = function (options) { 22 | opts = options; 23 | _setopts && _setopts.call(drawing, options); 24 | }; 25 | 26 | return drawing; 27 | 28 | 29 | // Called by DrawingBuiltin.init() to get the ARGB bitmap for rendering. 30 | function image(width, height) { 31 | canvas.width = width; 32 | canvas.height = height; 33 | 34 | // Set background 35 | ctx.setTransform(1, 0, 0, 1, 0, 0); 36 | if (/^[0-9a-fA-F]{6}$/.test(''+opts.backgroundcolor)) { 37 | ctx.fillStyle = '#' + opts.backgroundcolor; 38 | ctx.fillRect(0, 0, width, height); 39 | } else { 40 | ctx.clearRect(0, 0, width, height); 41 | } 42 | 43 | // Prepare the bitmap 44 | img = ctx.getImageData(0, 0, width, height); 45 | 46 | // The return value is designed for both canvas pure-RGBA and PNG RGBA 47 | return { buffer:img.data, ispng:false }; 48 | } 49 | 50 | function end() { 51 | ctx.putImageData(img, 0, 0); 52 | return canvas; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/drawing-svg.js: -------------------------------------------------------------------------------- 1 | // drawing-svg.js 2 | // 3 | // Converts the drawing primitives into the equivalent SVG. Linear barcodes 4 | // are rendered as a series of stroked paths. 2D barcodes are rendered as a 5 | // series of filled paths. 6 | // 7 | // Rotation is handled during drawing. The resulting SVG will contain the 8 | // already-rotated barcode without an SVG transform. 9 | // 10 | // If the requested barcode image contains text, the glyph paths are 11 | // extracted from the font file (via the builtin FontLib and stb_truetype.js) 12 | // and added as filled SVG paths. 13 | // 14 | function DrawingSVG() { 15 | // Unrolled x,y rotate/translate matrix 16 | var tx0 = 0, tx1 = 0, tx2 = 0, tx3 = 0; 17 | var ty0 = 0, ty1 = 0, ty2 = 0, ty3 = 0; 18 | 19 | var opts; 20 | var svg = ''; 21 | var path; 22 | var clipid = ''; 23 | var clips = []; 24 | var lines = {}; 25 | 26 | // We adjust the drawing coordinates by 0.5px when stroke width is odd. 27 | // But this creates an odd effect with scale. When scale is even, we 28 | // need to add 0.5; when scale is odd, subtract 0.5. 29 | var scalex, scaley; 30 | 31 | // Magic number to approximate an ellipse/circle using 4 cubic beziers. 32 | var ELLIPSE_MAGIC = 0.55228475 - 0.00045; 33 | 34 | // Global graphics state 35 | var gs_width, gs_height; // image size, in pixels 36 | var gs_dx, gs_dy; // x,y translate (padding) 37 | 38 | return { 39 | // setopts() is called after the options are fixed-up/normalized, 40 | // but before calling into BWIPP. 41 | // This allows omitting the options in the constructor call. 42 | // It is also your last chance to amend the options before usage. 43 | setopts(options) { 44 | opts = options; 45 | }, 46 | 47 | // measure() and scale() are the only drawing primitives that are called before init(). 48 | 49 | // Make no adjustments 50 | scale(sx, sy) { 51 | scalex = sx; 52 | scaley = sy; 53 | }, 54 | // Measure text. 55 | // `font` is the font name typically OCR-A or OCR-B. 56 | // `fwidth` and `fheight` are the requested font cell size. They will 57 | // usually be the same, except when the scaling is not symetric. 58 | measure(str, font, fwidth, fheight) { 59 | fwidth = fwidth|0; 60 | fheight = fheight|0; 61 | 62 | var fontid = FontLib.lookup(font); 63 | var width = 0; 64 | var ascent = 0; 65 | var descent = 0; 66 | for (var i = 0, l = str.length; i < l; i++) { 67 | var ch = str.charCodeAt(i); 68 | var glyph = FontLib.getglyph(fontid, ch, fwidth, fheight); 69 | if (!glyph) { 70 | continue; 71 | } 72 | ascent = Math.max(ascent, glyph.top); 73 | descent = Math.max(descent, glyph.height - glyph.top); 74 | if (i == l-1) { 75 | width += glyph.left + glyph.width; 76 | } else { 77 | width += glyph.advance; 78 | } 79 | } 80 | return { width, ascent, descent }; 81 | }, 82 | 83 | // `width` and `height` represent the maximum bounding box the graphics will 84 | // occupy. The dimensions are for an unrotated rendering. Adjust as necessary. 85 | init(width, height) { 86 | // Add in the effects of padding. These are always set before the 87 | // drawing constructor is called. 88 | var padl = opts.paddingleft; 89 | var padr = opts.paddingright; 90 | var padt = opts.paddingtop; 91 | var padb = opts.paddingbottom; 92 | var rot = opts.rotate || 'N'; 93 | 94 | width += padl + padr; 95 | height += padt + padb; 96 | 97 | // Transform indexes are: x, y, w, h 98 | switch (rot) { 99 | // tx = w-y, ty = x 100 | case 'R': tx1 = -1; tx2 = 1; ty0 = 1; break; 101 | // tx = w-x, ty = h-y 102 | case 'I': tx0 = -1; tx2 = 1; ty1 = -1; ty3 = 1; break; 103 | // tx = y, ty = h-x 104 | case 'L': tx1 = 1; ty0 = -1; ty3 = 1; break; 105 | // tx = x, ty = y 106 | default: tx0 = ty1 = 1; break; 107 | } 108 | 109 | // Setup the graphics state 110 | var swap = rot == 'L' || rot == 'R'; 111 | gs_width = swap ? height : width; 112 | gs_height = swap ? width : height; 113 | gs_dx = padl; 114 | gs_dy = padt; 115 | }, 116 | // Unconnected stroked lines are used to draw the bars in linear barcodes. 117 | // No line cap should be applied. These lines are always orthogonal. 118 | line(x0, y0, x1, y1, lw, rgb) { 119 | x0 = x0|0; 120 | y0 = y0|0; 121 | x1 = x1|0; 122 | y1 = y1|0; 123 | lw = Math.round(lw) || 1; 124 | 125 | // Try to keep the lines "crisp" by using with the SVG line drawing spec to 126 | // our advantage and adjust the coordinates by half pixel when stroke width 127 | // is odd. Work around an odd effect with scale. When scale is even, we 128 | // need to add 0.5; when scale is odd, subtract 0.5. 129 | if (lw & 1) { 130 | if (x0 == x1) { 131 | let dx = (scalex&1) ? -0.5 : 0.5; 132 | x0 += dx; 133 | x1 += dx; 134 | } 135 | if (y0 == y1) { 136 | let dy = (scaley&1) ? -0.5 : 0.5; 137 | y0 += dy; 138 | y1 += dy; 139 | } 140 | } 141 | // The svg path does not include the start pixel, but the built-in drawing does. 142 | if (x0 == x1) { 143 | y0++; 144 | } else if (y0 == y1) { 145 | x0++; 146 | } 147 | 148 | // Group together all lines of the same width and emit as single paths. 149 | // Dramatically reduces the svg text size. 150 | var key = '' + lw + '#' + rgb; 151 | if (!lines[key]) { 152 | lines[key] = '\n'; 213 | path = null; 214 | } 215 | }, 216 | // Currently only used by swissqrcode. The `polys` area is an array of 217 | // arrays of points. Each array of points is identical to the `pts` 218 | // parameter passed to polygon(). The clipping rule, like the fill rule, 219 | // defaults to non-zero winding. 220 | clip : function(polys) { 221 | var path = ''; 232 | clipid = "clip" + clips.length; 233 | clips.push(path); 234 | }, 235 | unclip : function() { 236 | clipid = ''; 237 | }, 238 | // Draw text with optional inter-character spacing. `y` is the baseline. 239 | // font is an object with properties { name, width, height, dx } 240 | // width and height are the font cell size. 241 | // dx is extra space requested between characters (usually zero). 242 | text(x, y, str, rgb, font) { 243 | var fontid = FontLib.lookup(font.name); 244 | var fwidth = font.width|0; 245 | var fheight = font.height|0; 246 | var dx = font.dx|0; 247 | var path = ''; 248 | for (var k = 0; k < str.length; k++) { 249 | var ch = str.charCodeAt(k); 250 | var glyph = FontLib.getpaths(fontid, ch, fwidth, fheight); 251 | if (!glyph) { 252 | continue; 253 | } 254 | if (glyph.length) { 255 | // A glyph is composed of sequence of curve and line segments. 256 | // M is move-to 257 | // L is line-to 258 | // Q is quadratic bezier curve-to 259 | // C is cubic bezier curve-to 260 | for (var i = 0, l = glyph.length; i < l; i++) { 261 | let seg = glyph[i]; 262 | if (seg.type == 'M' || seg.type == 'L') { 263 | path += seg.type + transform(seg.x + x, y - seg.y); 264 | } else if (seg.type == 'Q') { 265 | path += seg.type + transform(seg.cx + x, y - seg.cy) + ' ' + 266 | transform(seg.x + x, y - seg.y); 267 | } else if (seg.type == 'C') { 268 | path += seg.type + transform(seg.cx1 + x, y - seg.cy1) + ' ' + 269 | transform(seg.cx2 + x, y - seg.cy2) + ' ' + 270 | transform(seg.x + x, y - seg.y); 271 | } 272 | } 273 | // Close the shape 274 | path += 'Z'; 275 | } 276 | // getglyph() provides slightly different metrics than getpaths(). Keep 277 | // it consistent with the built-in drawing. 278 | x += FontLib.getglyph(fontid, ch, fwidth, fheight).advance + dx; 279 | } 280 | if (path) { 281 | svg += '\n'; 282 | } 283 | }, 284 | // Called after all drawing is complete. The return value from this method 285 | // will be the return value from `bwipjs.render()`. 286 | end() { 287 | var linesvg = ''; 288 | for (var key in lines) { 289 | linesvg += lines[key] + '" />\n'; 290 | } 291 | var bg = opts.backgroundcolor; 292 | return '\n' + 293 | (clips.length ? '' + clips.join('') + '' : '') + 294 | (/^[0-9A-Fa-f]{6}$/.test(''+bg) 295 | ? '\n' 296 | : '') + 297 | linesvg + svg + '\n'; 298 | }, 299 | }; 300 | 301 | // translate/rotate and return as an SVG coordinate pair 302 | function transform(x, y) { 303 | x += gs_dx; 304 | y += gs_dy; 305 | var tx = tx0 * x + tx1 * y + tx2 * (gs_width-1) + tx3 * (gs_height-1); 306 | var ty = ty0 * x + ty1 * y + ty2 * (gs_width-1) + ty3 * (gs_height-1); 307 | return '' + ((tx|0) == tx ? tx : tx.toFixed(2)) + ' ' + 308 | ((ty|0) == ty ? ty : ty.toFixed(2)); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/drawing-zlibpng.js: -------------------------------------------------------------------------------- 1 | // drawing-zlibpng.js 2 | // 3 | var PNGTYPE_PALETTE = 3; 4 | var PNGTYPE_TRUEALPHA = 6; 5 | var PNG_TEXT = "Software\0bwip-js.metafloor.com"; 6 | var PNG_CRC = (function() { 7 | var precalc = []; 8 | for (var i = 0; i < 256; i++) { 9 | var c = i; 10 | for (var j = 0; j < 8; j++) { 11 | if (c & 1) { 12 | c = 0xedb88320 ^ (c >>> 1); 13 | } else { 14 | c = c >>> 1; 15 | } 16 | } 17 | precalc[i] = c; 18 | } 19 | return precalc; 20 | })(); 21 | 22 | // This has been moved to the nodejs-only section of exports.js due to 23 | // react-native polyfills. 24 | //var PNG_ZLIB = require('zlib'); 25 | 26 | // `maybe` maybe the callback, pre v4.0. 27 | function DrawingZlibPng(callback, maybe) { 28 | // Pre setops() backward compatibility. 29 | if (maybe && typeof maybe == 'function') { 30 | callback = maybe; 31 | } 32 | var image_buffer, image_width, image_height; 33 | 34 | // Provide our specializations for the builtin drawing 35 | var drawing = DrawingBuiltin(); 36 | drawing.image = image; 37 | drawing.end = end; 38 | 39 | // Reflect setopts() into the super 40 | var opts; 41 | var _setopts = drawing.setopts; 42 | drawing.setopts = function (options) { 43 | opts = options; 44 | _setopts && _setopts.call(drawing, options); 45 | }; 46 | 47 | return drawing; 48 | 49 | // Called by DrawingBuiltin.init() to get the RGBA image data for rendering. 50 | function image(width, height) { 51 | // PNG RGBA buffers are prefixed with a one-byte filter type 52 | image_buffer = Buffer.alloc ? Buffer.alloc(width * height * 4 + height) 53 | : new Buffer(width * height * 4 + height); 54 | image_width = width; 55 | image_height = height; 56 | 57 | // Set background 58 | if (/^[0-9a-fA-F]{6}$/.test(''+opts.backgroundcolor)) { 59 | var rgb = opts.backgroundcolor; 60 | fillRGB(parseInt(rgb.substr(0,2), 16), 61 | parseInt(rgb.substr(2,2), 16), 62 | parseInt(rgb.substr(4,2), 16)); 63 | } 64 | 65 | // The return value is designed to accommodate both canvas pure-RGBA buffers 66 | // and PNG's row-filter prefixed RGBA buffers. 67 | return { buffer:image_buffer, ispng:true }; 68 | } 69 | 70 | function fillRGB(r, g, b) { 71 | var color = ((r << 24) | (g << 16) | (b << 8) | 0xff) >>> 0; 72 | 73 | // This is made complex by the filter byte that prefixes each row... 74 | var len = image_width * 4 + 1; 75 | var row = Buffer.alloc ? Buffer.alloc(len) : new Buffer(len); 76 | for (var i = 1; i < len; i += 4) { 77 | row.writeUInt32BE(color, i); 78 | } 79 | image_buffer.fill(row); 80 | } 81 | 82 | function end() { 83 | if (!callback) { 84 | return new Promise(makePNG); 85 | } else { 86 | makePNG(function(png) { callback(null, png); }, function(err) { callback(err); }); 87 | } 88 | } 89 | 90 | function makePNG(resolve, reject) { 91 | // DEFLATE the image data 92 | var bufs = []; 93 | var buflen = 0; 94 | var deflator = PNG_ZLIB.createDeflate({ 95 | chunkSize: 32 * 1024, 96 | level : PNG_ZLIB.Z_DEFAULT_COMPRESSION, 97 | strategy: PNG_ZLIB.Z_DEFAULT_STRATEGY }); 98 | deflator.on('error', reject); 99 | deflator.on('data', function(data) { bufs.push(data); buflen += data.length; }); 100 | deflator.on('end', returnPNG); 101 | deflator.end(image_buffer); 102 | 103 | function returnPNG() { 104 | var length = 8 + 12 + 13 + // PNG Header + IHDR chunk 105 | 12 + PNG_TEXT.length + // tEXt 106 | 12 + buflen + // IDAT 107 | 12; // IEND 108 | if (opts.dpi) { 109 | length += 12 + 9; // pHYs 110 | } 111 | 112 | // Emulate a byte-stream 113 | var png = Buffer.alloc(length); 114 | var pngoff = 0; // running offset into the png buffer 115 | 116 | write('\x89PNG\x0d\x0a\x1a\x0a'); // PNG file header 117 | writeIHDR(); 118 | writeTEXT(); 119 | if (opts.dpi) { 120 | writePHYS(); 121 | } 122 | writeIDAT(); 123 | writeIEND(); 124 | 125 | // Success 126 | resolve(png); 127 | 128 | function writeIHDR() { 129 | write32(13); // chunk length 130 | var crcoff = pngoff; 131 | 132 | write('IHDR'); 133 | write32(image_width); 134 | write32(image_height); 135 | write8(8); // bit depth 136 | write8(PNGTYPE_TRUEALPHA); 137 | write8(0); // compression default 138 | write8(0); // filter default 139 | write8(0); // no interlace 140 | 141 | writeCRC(crcoff); 142 | } 143 | function writeTEXT() { 144 | write32(PNG_TEXT.length); // chunk length 145 | var crcoff = pngoff; 146 | 147 | write('tEXt'); 148 | write(PNG_TEXT); 149 | writeCRC(crcoff); 150 | } 151 | function writePHYS() { 152 | write32(9); 153 | var crcoff = pngoff; 154 | 155 | var pxm = ((opts.dpi || 72) / 0.0254)|0; 156 | write('pHYs'); 157 | write32(pxm); // x-axis 158 | write32(pxm); // y-axis 159 | write8(1); // px/m (the only usable option) 160 | writeCRC(crcoff); 161 | } 162 | function writeIDAT() { 163 | write32(buflen); // chunk length 164 | var crcoff = pngoff; 165 | 166 | write('IDAT'); 167 | for (var i = 0; i < bufs.length; i++) { 168 | bufs[i].copy(png, pngoff); 169 | pngoff += bufs[i].length; 170 | } 171 | writeCRC(crcoff); 172 | } 173 | function writeIEND() { 174 | write32(0); // chunk length; 175 | var crcoff = pngoff; 176 | 177 | write('IEND'); 178 | writeCRC(crcoff); 179 | } 180 | 181 | function write(s) { 182 | png.write(s, pngoff, 'binary'); 183 | pngoff += s.length; 184 | } 185 | function write32(v) { 186 | png.writeUInt32BE(v, pngoff); 187 | pngoff += 4; 188 | } 189 | function write16(v) { 190 | png.writeUInt16BE(v, pngoff); 191 | pngoff += 2; 192 | } 193 | function write8(v) { 194 | png[pngoff++] = v; 195 | } 196 | function writeCRC(off) { 197 | var crc = -1; 198 | while (off < pngoff) { 199 | crc = PNG_CRC[(crc ^ png[off++]) & 0xff] ^ (crc >>> 8); 200 | } 201 | write32((crc ^ -1) >>> 0); 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/exports.js: -------------------------------------------------------------------------------- 1 | 2 | // exports.js 3 | const BWIPJS_VERSION = '__BWIPJS_VERS__'; 4 | 5 | //@@BEGIN-NODE-JS-EXPORTS@@ 6 | var url = require('url'); 7 | var PNG_ZLIB = require('zlib'); 8 | require('stream'); // fix for https://github.com/nodejs/node/issues/37021 9 | 10 | // bwipjs.request(req, res [, overrides]) 11 | // 12 | // Returns a PNG image from the query args of a node.js http request object. 13 | // 14 | // This function is asynchronous. 15 | function Request(req, res, extra) { 16 | var opts = url.parse(req.url, true).query; 17 | 18 | // Convert empty !parameters to false. 19 | // Convert empty parameters to true. 20 | for (var id in opts) { 21 | if (opts[id] === '') { 22 | if (id[0] == '!') { 23 | opts[id.substr(1)] = false; 24 | } else { 25 | opts[id] = true; 26 | } 27 | } 28 | } 29 | 30 | // Add in server options/overrides 31 | if (extra) { 32 | for (var id in extra) { 33 | opts[id] = extra[id]; 34 | } 35 | } 36 | 37 | ToBuffer(opts, function(err, png) { 38 | if (err) { 39 | res.writeHead(400, { 'Content-Type':'text/plain' }); 40 | res.end('' + (err.stack || err), 'utf-8'); 41 | } else { 42 | res.writeHead(200, { 'Content-Type':'image/png' }); 43 | res.end(png, 'binary'); 44 | } 45 | }); 46 | } 47 | 48 | // bwipjs.toBuffer(options[, callback]) 49 | // 50 | // Uses the built-in graphics drawing and zlib PNG encoding to return a 51 | // barcode image in a node.js Buffer. 52 | // 53 | // `options` are a bwip-js/BWIPP options object. 54 | // `callback` is an optional callback handler with prototype: 55 | // 56 | // function callback(err, png) 57 | // 58 | // `err` is an Error object or string. If `err` is set, `png` is null. 59 | // `png` is a node Buffer containing the PNG image. 60 | // 61 | // If `callback` is not provided, a Promise is returned. 62 | function ToBuffer(opts, callback) { 63 | return _ToAny(bwipp_lookup(opts.bcid), opts, callback); 64 | } 65 | 66 | // Entry point for the symbol-specific exports. 67 | // 68 | // Polymorphic internal interface 69 | // _ToAny(encoder, opts) : Promise 70 | // _ToAny(endoder, opts, drawing) : any !throws! 71 | // _ToAny(encoder, opts, callback) : void 72 | // 73 | // If `drawing` is not provided or `callback` is, the built-in DrawingZlibPng will be used. 74 | function _ToAny(encoder, opts, drawing) { 75 | var callback; 76 | if (typeof drawing == 'function') { 77 | callback = drawing; 78 | drawing = null 79 | } 80 | if (drawing) { 81 | return _Render(encoder, opts, drawing); 82 | } else if (callback) { 83 | try { 84 | _Render(encoder, opts, DrawingZlibPng(callback)); 85 | } catch (e) { 86 | callback(e); 87 | } 88 | } else { 89 | return new Promise(function (resolve, reject) { 90 | _Render(encoder, opts, DrawingZlibPng(function (err, buf) { 91 | err ? reject(err) : resolve(buf); 92 | })); 93 | }); 94 | } 95 | } 96 | //@@BEGIN-BROWSER-EXPORTS@@ 97 | // bwipjs.toCanvas(canvas, options) 98 | // bwipjs.toCanvas(options, canvas) 99 | // 100 | // Uses the built-in canvas drawing. 101 | // 102 | // `canvas` can be an HTMLCanvasElement or an ID string or unique selector string. 103 | // `options` are a bwip-js/BWIPP options object. 104 | // 105 | // This function is synchronous and throws on error. 106 | // 107 | // Returns the HTMLCanvasElement. 108 | function ToCanvas(cvs, opts) { 109 | if (typeof opts == 'string' || opts instanceof HTMLCanvasElement) { 110 | let tmp = cvs; 111 | cvs = opts; 112 | opts = tmp; 113 | } 114 | return _ToAny(bwipp_lookup(opts.bcid), opts, cvs); 115 | } 116 | // Entry point for the symbol-specific exports 117 | // 118 | // Polymorphic internal interface 119 | // _ToAny(encoder, string, opts) : HTMLCanvasElement 120 | // _ToAny(encoder, HTMLCanvasElement, opts) : HTMLCanvasElement 121 | // _ToAny(encoder, opts, string) : HTMLCanvasElement 122 | // _ToAny(encoder, opts, HTMLCanvasElement) : HTMLCanvasElement 123 | // _ToAny(encoder, opts, drawing) : any 124 | // 125 | // 'string` can be either an `id` or query selector returning a single canvas element. 126 | function _ToAny(encoder, opts, drawing) { 127 | if (typeof opts == 'string') { 128 | var canvas = document.getElementById(opts) || document.querySelector(opts); 129 | if (!(canvas instanceof HTMLCanvasElement)) { 130 | throw new Error('bwipjs: `' + opts + '`: not a canvas'); 131 | } 132 | opts = drawing; 133 | drawing = DrawingCanvas(canvas); 134 | } else if (opts instanceof HTMLCanvasElement) { 135 | var canvas = opts; 136 | opts = drawing; 137 | drawing = DrawingCanvas(canvas); 138 | } else if (typeof drawing == 'string') { 139 | var canvas = document.getElementById(drawing) || document.querySelector(drawing); 140 | if (!(canvas instanceof HTMLCanvasElement)) { 141 | throw new Error('bwipjs: `' + drawing + '`: not a canvas'); 142 | } 143 | drawing = DrawingCanvas(canvas); 144 | } else if (drawing instanceof HTMLCanvasElement) { 145 | drawing = DrawingCanvas(drawing); 146 | } else if (!drawing || typeof drawing != 'object' || !drawing.init) { 147 | throw new Error('bwipjs: not a canvas or drawing object'); 148 | } 149 | return _Render(encoder, opts, drawing); 150 | } 151 | //@@BEGIN-REACT-NV-EXPORTS@@ 152 | import PNG_ZLIB from 'react-zlib-js'; 153 | import Buffer from 'react-zlib-js/buffer.js'; 154 | 155 | // bwipjs.toDataURL(options[, callback]) 156 | // 157 | // Uses the built-in graphics drawing and zlib PNG encoding to generate a 158 | // barcode image. 159 | // 160 | // `options` are a bwip-js/BWIPP options object. 161 | // `callback` is an optional callback handler with prototype: 162 | // 163 | // function callback(err, png) 164 | // 165 | // `err` is an Error object or string. If `err` is set, `png` is null. 166 | // `png` is an object with properties: 167 | // `width` : The width of the image, in pixels. 168 | // `height` : The height of the image, in pixels. 169 | // `uri` : A base64 encoded data URL. 170 | // 171 | // If `callback` is not provided, a Promise is returned. 172 | function ToDataURL(opts, callback) { 173 | return _ToAny(bwipp_lookup(opts.bcid), opts, callback); 174 | } 175 | 176 | // Polymorphic internal interface 177 | // _ToAny(encoder, opts) : Promise 178 | // _ToAny(encoder, opts, callback) : void 179 | // _ToAny(endoder, opts, drawing) : any !throws! 180 | // 181 | // If `drawing` is not provided, the built-in DrawingZlibPng will be used. 182 | function _ToAny(encoder, opts, drawing) { 183 | var callback; 184 | if (typeof drawing == 'function') { 185 | callback = drawing; 186 | drawing = null 187 | } 188 | if (drawing) { 189 | return _Render(encoder, opts, drawing); 190 | } else if (callback) { 191 | try { 192 | _Render(encoder, opts, DrawingZlibPng((err, buf) => { 193 | if (err) { 194 | callback(err); 195 | } else { 196 | callback(null, { 197 | width:buf.readUInt32BE(16), 198 | height:buf.readUInt32BE(20), 199 | uri:'data:image/png;base64,' + buf.toString('base64') 200 | }); 201 | } 202 | })); 203 | } catch (e) { 204 | callback(e); 205 | } 206 | } else { 207 | return new Promise(function (resolve, reject) { 208 | _Render(encoder, opts, DrawingZlibPng((err, buf) => { 209 | if (err) { 210 | reject(err); 211 | } else { 212 | resolve({ 213 | width:buf.readUInt32BE(16), 214 | height:buf.readUInt32BE(20), 215 | uri:'data:image/png;base64,' + buf.toString('base64') 216 | }); 217 | } 218 | })); 219 | }); 220 | } 221 | } 222 | // Specialized DataURL version of DrawingZlibPng() 223 | function DrawingDataURL(opts, callback) { 224 | if (callback) { 225 | return DrawingZlibPng((err, buf) => { 226 | if (err) { 227 | callback(err); 228 | } else { 229 | callback(null, { 230 | width:buf.readUInt32BE(16), 231 | height:buf.readUInt32BE(20), 232 | uri:'data:image/png;base64,' + buf.toString('base64') 233 | }); 234 | } 235 | }); 236 | } else { 237 | return new Promise((resolve, reject) => { 238 | DrawingZlibPng((err, buf) => { 239 | if (err) { 240 | reject(err); 241 | } else { 242 | resolve({ 243 | width:buf.readUInt32BE(16), 244 | height:buf.readUInt32BE(20), 245 | uri:'data:image/png;base64,' + buf.toString('base64') 246 | }); 247 | } 248 | }) 249 | }); 250 | } 251 | } 252 | //@@ENDOF-EXPORTS@@ 253 | 254 | // bwipjs.toSVG(options) 255 | // 256 | // Uses the built-in svg drawing interface. 257 | // 258 | // `options` are a bwip-js/BWIPP options object. 259 | // 260 | // This function is synchronous and throws on error. 261 | // 262 | // Returns a string containing a fully qualified SVG definition, 263 | // including the natural width and height of the image, in pixels: 264 | // 265 | // 266 | // ... 267 | // 268 | // 269 | // Available on all platforms. 270 | function ToSVG(opts) { 271 | return _Render(bwipp_lookup(opts.bcid), opts, DrawingSVG()); 272 | } 273 | 274 | function FixupOptions(opts) { 275 | var scale = opts.scale || 2; 276 | var scaleX = +opts.scaleX || scale; 277 | var scaleY = +opts.scaleY || scaleX; 278 | 279 | // Fix up padding. 280 | opts.paddingleft = padding(opts.paddingleft, opts.paddingwidth, opts.padding, scaleX); 281 | opts.paddingright = padding(opts.paddingright, opts.paddingwidth, opts.padding, scaleX); 282 | opts.paddingtop = padding(opts.paddingtop, opts.paddingheight, opts.padding, scaleY); 283 | opts.paddingbottom = padding(opts.paddingbottom, opts.paddingheight, opts.padding, scaleY); 284 | 285 | // We override BWIPP's background color functionality. If in CMYK, convert to RRGGBB so 286 | // the drawing interface is consistent. Likewise, if in CSS-style #rgb or #rrggbb. 287 | if (opts.backgroundcolor) { 288 | var bgc = ''+opts.backgroundcolor; 289 | if (/^[0-9a-fA-F]{8}$/.test(bgc)) { 290 | var c = parseInt(bgc.substr(0,2), 16) / 255; 291 | var m = parseInt(bgc.substr(2,2), 16) / 255; 292 | var y = parseInt(bgc.substr(4,2), 16) / 255; 293 | var k = parseInt(bgc.substr(6,2), 16) / 255; 294 | var r = Math.floor((1-c) * (1-k) * 255).toString(16); 295 | var g = Math.floor((1-m) * (1-k) * 255).toString(16); 296 | var b = Math.floor((1-y) * (1-k) * 255).toString(16); 297 | opts.backgroundcolor = (r.length == 1 ? '0' : '') + r + 298 | (g.length == 1 ? '0' : '') + g + 299 | (b.length == 1 ? '0' : '') + b; 300 | } else { 301 | if (bgc[0] == '#') { 302 | bgc = bgc.substr(1); 303 | } 304 | if (/^[0-9a-fA-F]{6}$/.test(bgc)) { 305 | opts.backgroundcolor = bgc; 306 | } else if (/^[0-9a-fA-F]{3}$/.test(bgc)) { 307 | opts.backgroundcolor = bgc[0] + bgc[0] + bgc[1] + bgc[1] + bgc[2] + bgc[2]; 308 | } else { 309 | throw new Error('bwip-js: invalid backgroundcolor: ' + opts.backgroundcolor); 310 | } 311 | } 312 | } 313 | 314 | return opts; 315 | 316 | // a is the most specific padding value, e.g. paddingleft 317 | // b is the next most specific value, e.g. paddingwidth 318 | // c is the general padding value. 319 | // s is the scale, either scalex or scaley 320 | function padding(a, b, c, s) { 321 | if (a != null) { 322 | a = a >>> 0; 323 | return a*s >>> 0; 324 | } 325 | if (b != null) { 326 | b = b >>> 0; 327 | return b*s >>> 0; 328 | } 329 | c = c >>> 0; 330 | return (c*s >>> 0) || 0; 331 | } 332 | } 333 | 334 | var BWIPJS_OPTIONS = { 335 | bcid:1, 336 | text:1, 337 | scale:1, 338 | scaleX:1, 339 | scaleY:1, 340 | rotate:1, 341 | padding:1, 342 | paddingwidth:1, 343 | paddingheight:1, 344 | paddingtop:1, 345 | paddingleft:1, 346 | paddingright:1, 347 | paddingbottom:1, 348 | backgroundcolor:1, 349 | }; 350 | 351 | // bwipjs.render(options, drawing) 352 | // 353 | // Renders a barcode using the provided drawing object. 354 | // 355 | // This function is synchronous and throws on error. 356 | // 357 | // Browser and nodejs usage. 358 | function Render(options, drawing) { 359 | return _Render(bwipp_lookup(options.bcid), options, drawing); 360 | } 361 | 362 | // Called by the public exports 363 | function _Render(encoder, options, drawing) { 364 | var text = options.text; 365 | if (!text) { 366 | throw new ReferenceError('bwip-js: bar code text not specified.'); 367 | } 368 | 369 | // setopts() is optional on the drawing object. 370 | FixupOptions(options); 371 | drawing.setopts && drawing.setopts(options); 372 | 373 | // Set the bwip-js defaults 374 | var scale = options.scale || 2; 375 | var scaleX = +options.scaleX || scale; 376 | var scaleY = +options.scaleY || scaleX; 377 | var rotate = options.rotate || 'N'; 378 | 379 | // Create a barcode writer object. This is the interface between 380 | // the low-level BWIPP code, the bwip-js graphics context, and the 381 | // drawing interface. 382 | var bw = new BWIPJS(drawing); 383 | 384 | // Set the BWIPP options 385 | var bwippopts = {}; 386 | for (var id in options) { 387 | if (!BWIPJS_OPTIONS[id]) { 388 | bwippopts[id] = options[id]; 389 | } 390 | } 391 | 392 | // Fix a disconnect in the BWIPP rendering logic 393 | if (bwippopts.alttext) { 394 | bwippopts.includetext = true; 395 | } 396 | // We use mm rather than inches for height - except pharmacode2 height 397 | // which is already in mm. 398 | if (+bwippopts.height && encoder != bwipp_pharmacode2) { 399 | bwippopts.height = bwippopts.height / 25.4 || 0.5; 400 | } 401 | // Likewise, width 402 | if (+bwippopts.width) { 403 | bwippopts.width = bwippopts.width / 25.4 || 0; 404 | } 405 | 406 | // Scale the image 407 | bw.scale(scaleX, scaleY); 408 | 409 | // Call into the BWIPP cross-compiled code and render the image. 410 | bwipp_encode(bw, encoder, text, bwippopts); 411 | 412 | // Returns whatever drawing.end() returns, or `false` if nothing rendered. 413 | return bw.render(); 414 | } 415 | 416 | // bwipjs.raw(options) 417 | // bwipjs.raw(bcid, text, opts-string) 418 | // 419 | // Invokes the low level BWIPP code and returns the raw encoding data. 420 | // 421 | // This function is synchronous and throws on error. 422 | // 423 | // Browser and nodejs usage. 424 | function ToRaw(bcid, text, options) { 425 | if (arguments.length == 1) { 426 | options = bcid; 427 | bcid = options.bcid; 428 | text = options.text; 429 | } 430 | 431 | // The drawing interface is just needed for the pre-init() calls. 432 | // Don't need to fixup the drawing specific options. 433 | var drawing = DrawingBuiltin(); 434 | drawing.setopts(options); 435 | 436 | var bw = new BWIPJS(drawing); 437 | var stack = bwipp_encode(bw, bwipp_lookup(bcid), text, options, true); 438 | 439 | // bwip-js uses Maps to emulate PostScript dictionary objects; but Maps 440 | // are not a typical/expected return value. Convert to plain-old-objects. 441 | var ids = { pixs:1, pixx:1, pixy:1, sbs:1, bbs:1, bhs:1, width:1, height:1 }; 442 | for (var i = 0; i < stack.length; i++) { 443 | var elt = stack[i]; 444 | if (elt instanceof Map) { 445 | var obj = {}; 446 | // Could they make Maps any harder to iterate over??? 447 | for (var keys = elt.keys(), size = elt.size, k = 0; k < size; k++) { 448 | var id = keys.next().value; 449 | if (ids[id]) { 450 | var val = elt.get(id); 451 | if (val instanceof Array) { 452 | // The postscript arrays have extra named properties 453 | // to emulate array views. Return cleaned up arrays. 454 | obj[id] = val.b.slice(val.o, val.o + val.length); 455 | } else { 456 | obj[id] = val; 457 | } 458 | } 459 | } 460 | stack[i] = obj; 461 | } else { 462 | // This should never exec... 463 | stack.splice(i--, 1); 464 | } 465 | } 466 | return stack; 467 | } 468 | -------------------------------------------------------------------------------- /src/fontlib.js: -------------------------------------------------------------------------------- 1 | // fontlib.js 2 | var FontLib = (function() { 3 | var fonts = []; 4 | var names = {}; 5 | var glyphcache = {}; 6 | var glyphmru = {}; 7 | var glyphcount = 0; 8 | 9 | // Sentinel to simplify moving entries around in the list. 10 | glyphmru.next = glyphmru; 11 | glyphmru.prev = glyphmru; 12 | 13 | return { 14 | lookup:lookup, 15 | monochrome:monochrome, 16 | getglyph:getglyph, 17 | getpaths:getpaths, 18 | loadFont:loadFont, 19 | }; 20 | 21 | // loadFont(name, data) 22 | // loadFont(name, mult, data) 23 | // loadFont(name, multy, multx, data) // note order: y,x 24 | // data must be the font data, either a binary or base64 encoded string. 25 | function loadFont(name /*...args*/) { 26 | var multx = 100; 27 | var multy = 100; 28 | var data = null; 29 | 30 | if (arguments.length == 2) { 31 | data = arguments[1]; 32 | } else if (arguments.length == 3) { 33 | multx = multy = +arguments[1] || 100; 34 | data = arguments[2]; 35 | } else if (arguments.length == 4) { 36 | multy = +arguments[1] || 100; 37 | multx = +arguments[2] || 100; 38 | data = arguments[3]; 39 | } else { 40 | throw new Error("bwipjs: loadFont: invalid number of arguments"); 41 | } 42 | 43 | var font = STBTT.InitFont(toUint8Array(data)); 44 | font.bwipjs_name = name; 45 | font.bwipjs_multx = multx; 46 | font.bwipjs_multy = multy; 47 | 48 | var fontid = fonts.push(font)-1; 49 | names[name.toUpperCase()] = fontid; 50 | return fontid; 51 | } 52 | 53 | // Always returns a valid font-id (default OCR-B) 54 | function lookup(name) { 55 | var fontid = names[name.toUpperCase()]; 56 | return fontid === undefined ? 1 : fontid; // OCR B default 57 | } 58 | 59 | // Not supported by stbtt 60 | function monochrome(mono) { 61 | if (mono) { 62 | throw new Error('bwipjs: monochrome fonts not implemented'); 63 | } 64 | } 65 | 66 | function getglyph(fontid, charcode, width, height) { 67 | fontid = fontid|0; 68 | charcode = charcode|0; 69 | width = +width; 70 | height = +height; 71 | if (!width || width < 8) { 72 | width = 8; 73 | } 74 | if (!height || height < 8) { 75 | height = width; 76 | } 77 | if (fontid < 0 || fontid >= fonts.length) { 78 | fontid = 1; // OCR B default 79 | } 80 | if (!charcode || charcode < 32) { 81 | charcode = 32; 82 | } 83 | 84 | // In the cache? 85 | var cachekey = '' + fontid + 'c' + charcode + 'w' + width + 'h' + height; 86 | var glyph = glyphcache[cachekey]; 87 | if (glyph) { 88 | // Unthread from the MRU 89 | glyph.prev.next = glyph.next; 90 | glyph.next.prev = glyph.prev; 91 | 92 | // Thread back onto the top 93 | var sntl = glyphmru; 94 | sntl.next.prev = glyph; 95 | glyph.next = sntl.next; 96 | glyph.prev = sntl; 97 | sntl.next = glyph; 98 | 99 | return glyph; 100 | } 101 | 102 | var font = fonts[fontid]; 103 | var glyph = STBTT.GetGlyph(font, charcode, width * font.bwipjs_multx / 100, 104 | height * font.bwipjs_multy / 100) || 105 | STBTT.GetGlyph(font, 0, width * font.bwipjs_multx / 100, 106 | height * font.bwipjs_multy / 100); 107 | 108 | glyph.bytes = glyph.pixels; 109 | glyph.cachekey = cachekey; 110 | glyph.offset = 0; 111 | 112 | //glyph = { 113 | // top:font.GlyphTop(), 114 | // left:font.GlyphLeft(), 115 | // width:font.GlyphWidth(), 116 | // height:font.GlyphHeight(), 117 | // advance:font.GlyphAdvance(), 118 | // bitmap:font.GlyphBitmap(), 119 | // offset:0, 120 | // cachekey:cachekey, 121 | // }; 122 | 123 | // Purge old 124 | if (glyphcount > 250) { 125 | var sntl = glyphmru; 126 | var temp = sntl.prev; 127 | temp.prev.next = sntl; 128 | sntl.prev = temp.prev; 129 | temp.next = temp.prev = null; 130 | delete glyphcache[temp.cachekey]; 131 | } else { 132 | glyphcount++; 133 | } 134 | 135 | // Add to cache and to the top of the MRU 136 | glyphcache[cachekey] = glyph; 137 | 138 | var sntl = glyphmru; 139 | sntl.next.prev = glyph; 140 | glyph.next = sntl.next; 141 | glyph.prev = sntl; 142 | sntl.next = glyph; 143 | 144 | return glyph; 145 | } 146 | 147 | function getpaths(fontid, charcode, width, height) { 148 | fontid = fontid|0; 149 | charcode = charcode|0; 150 | width = +width; 151 | height = +height; 152 | if (!width || width < 8) { 153 | width = 8; 154 | } 155 | if (!height || height < 8) { 156 | height = width; 157 | } 158 | if (fontid < 0 || fontid >= fonts.length) { 159 | fontid = 1; // OCR B default 160 | } 161 | if (!charcode || charcode < 32) { 162 | charcode = 32; 163 | } 164 | 165 | var font = fonts[fontid]; 166 | return STBTT.GetPaths(font, charcode, width * font.bwipjs_multx / 100, 167 | height * font.bwipjs_multy / 100); 168 | } 169 | })(); 170 | 171 | // This is needed to make the default exports traceable by esbuild 172 | // during its tree shaking phase. See issue #298. 173 | function LoadFont() { 174 | return FontLib.loadFont.apply(FontLib, Array.prototype.slice.call(arguments)); 175 | } 176 | --------------------------------------------------------------------------------