├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── canvas2svg.js ├── demo ├── avatars.png ├── demo_node.js ├── hamburger.png ├── logo-transparent.png ├── logo.png ├── q.jpg ├── q.png ├── q2.jpg ├── q2.png ├── q3.jpg ├── q3.png ├── q4.jpg ├── q4.png ├── qrcode-stream.png └── qrcode.svg ├── doc └── images │ ├── QR_Code_Structure.png │ ├── QR_Code_Structure.svg │ ├── demo-premium.png │ └── demo.png ├── index.d.ts ├── index.js ├── index.min.js ├── package.json └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['http://www.easyproject.cn/donation'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Temp 64 | temp 65 | 66 | # Test 67 | test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /canvas2svg.js: -------------------------------------------------------------------------------- 1 | /*!! 2 | * Canvas 2 Svg for EasyQRCodeJS-NodeJS 3 | * A low level canvas to SVG converter. Uses a mock canvas context to build an SVG document. 4 | * 5 | */ 6 | ;(function () { 7 | "use strict"; 8 | 9 | var STYLES, ctx, CanvasGradient, CanvasPattern, namedEntities; 10 | 11 | //helper function to format a string 12 | function format(str, args) { 13 | var keys = Object.keys(args), 14 | i; 15 | for (i = 0; i < keys.length; i++) { 16 | str = str.replace( 17 | new RegExp("\\{" + keys[i] + "\\}", "gi"), 18 | args[keys[i]] 19 | ); 20 | } 21 | return str; 22 | } 23 | 24 | //helper function that generates a random string 25 | function randomString(holder) { 26 | var chars, randomstring, i; 27 | if (!holder) { 28 | throw new Error( 29 | "cannot create a random attribute name for an undefined object" 30 | ); 31 | } 32 | chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; 33 | randomstring = ""; 34 | do { 35 | randomstring = ""; 36 | for (i = 0; i < 12; i++) { 37 | randomstring += chars[Math.floor(Math.random() * chars.length)]; 38 | } 39 | } while (holder[randomstring]); 40 | return randomstring; 41 | } 42 | 43 | //helper function to map named to numbered entities 44 | function createNamedToNumberedLookup(items, radix) { 45 | var i, 46 | entity, 47 | lookup = {}, 48 | base10, 49 | base16; 50 | items = items.split(","); 51 | radix = radix || 10; 52 | // Map from named to numbered entities. 53 | for (i = 0; i < items.length; i += 2) { 54 | entity = "&" + items[i + 1] + ";"; 55 | base10 = parseInt(items[i], radix); 56 | lookup[entity] = "&#" + base10 + ";"; 57 | } 58 | //FF and IE need to create a regex from hex values ie   == \xa0 59 | lookup["\\xa0"] = " "; 60 | return lookup; 61 | } 62 | 63 | //helper function to map canvas-textAlign to svg-textAnchor 64 | function getTextAnchor(textAlign) { 65 | //TODO: support rtl languages 66 | var mapping = { 67 | left: "start", 68 | right: "end", 69 | center: "middle", 70 | start: "start", 71 | end: "end", 72 | }; 73 | return mapping[textAlign] || mapping.start; 74 | } 75 | 76 | //helper function to map canvas-textBaseline to svg-dominantBaseline 77 | function getDominantBaseline(textBaseline) { 78 | //INFO: not supported in all browsers 79 | var mapping = { 80 | alphabetic: "alphabetic", 81 | hanging: "hanging", 82 | top: "text-before-edge", 83 | bottom: "text-after-edge", 84 | middle: "central", 85 | }; 86 | return mapping[textBaseline] || mapping.alphabetic; 87 | } 88 | 89 | // Unpack entities lookup where the numbers are in radix 32 to reduce the size 90 | // entity mapping courtesy of tinymce 91 | namedEntities = createNamedToNumberedLookup( 92 | "50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy," + 93 | "5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute," + 94 | "5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34," + 95 | "5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil," + 96 | "68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde," + 97 | "6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute," + 98 | "6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml," + 99 | "75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc," + 100 | "7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash," + 101 | "7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta," + 102 | "sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu," + 103 | "st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi," + 104 | "t9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota," + 105 | "tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau," + 106 | "u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip," + 107 | "81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym," + 108 | "8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr," + 109 | "8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod," + 110 | "8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup," + 111 | "8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4," + 112 | "nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob," + 113 | "rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0," + 114 | "Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm," + 115 | "80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger," + 116 | "811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro", 117 | 32 118 | ); 119 | 120 | //Some basic mappings for attributes and default values. 121 | STYLES = { 122 | strokeStyle: { 123 | svgAttr: "stroke", //corresponding svg attribute 124 | canvas: "#000000", //canvas default 125 | svg: "none", //svg default 126 | apply: "stroke", //apply on stroke() or fill() 127 | }, 128 | fillStyle: { 129 | svgAttr: "fill", 130 | canvas: "#000000", 131 | svg: null, //svg default is black, but we need to special case this to handle canvas stroke without fill 132 | apply: "fill", 133 | }, 134 | lineCap: { 135 | svgAttr: "stroke-linecap", 136 | canvas: "butt", 137 | svg: "butt", 138 | apply: "stroke", 139 | }, 140 | lineJoin: { 141 | svgAttr: "stroke-linejoin", 142 | canvas: "miter", 143 | svg: "miter", 144 | apply: "stroke", 145 | }, 146 | miterLimit: { 147 | svgAttr: "stroke-miterlimit", 148 | canvas: 10, 149 | svg: 4, 150 | apply: "stroke", 151 | }, 152 | lineWidth: { 153 | svgAttr: "stroke-width", 154 | canvas: 1, 155 | svg: 1, 156 | apply: "stroke", 157 | }, 158 | globalAlpha: { 159 | svgAttr: "opacity", 160 | canvas: 1, 161 | svg: 1, 162 | apply: "fill stroke", 163 | }, 164 | font: { 165 | //font converts to multiple svg attributes, there is custom logic for this 166 | canvas: "10px sans-serif", 167 | }, 168 | shadowColor: { 169 | canvas: "#000000", 170 | }, 171 | shadowOffsetX: { 172 | canvas: 0, 173 | }, 174 | shadowOffsetY: { 175 | canvas: 0, 176 | }, 177 | shadowBlur: { 178 | canvas: 0, 179 | }, 180 | textAlign: { 181 | canvas: "start", 182 | }, 183 | textBaseline: { 184 | canvas: "alphabetic", 185 | }, 186 | lineDash: { 187 | svgAttr: "stroke-dasharray", 188 | canvas: [], 189 | svg: null, 190 | apply: "stroke", 191 | }, 192 | }; 193 | 194 | /** 195 | * 196 | * @param gradientNode - reference to the gradient 197 | * @constructor 198 | */ 199 | CanvasGradient = function (gradientNode, ctx) { 200 | this.__root = gradientNode; 201 | this.__ctx = ctx; 202 | }; 203 | 204 | /** 205 | * Adds a color stop to the gradient root 206 | */ 207 | CanvasGradient.prototype.addColorStop = function (offset, color) { 208 | var stop = this.__ctx.__createElement("stop"), 209 | regex, 210 | matches; 211 | stop.setAttribute("offset", offset); 212 | if (color.indexOf("rgba") !== -1) { 213 | //separate alpha value, since webkit can't handle it 214 | regex = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi; 215 | matches = regex.exec(color); 216 | stop.setAttribute( 217 | "stop-color", 218 | format("rgb({r},{g},{b})", { 219 | r: matches[1], 220 | g: matches[2], 221 | b: matches[3], 222 | }) 223 | ); 224 | stop.setAttribute("stop-opacity", matches[4]); 225 | } else { 226 | stop.setAttribute("stop-color", color); 227 | } 228 | this.__root.appendChild(stop); 229 | }; 230 | 231 | CanvasPattern = function (pattern, ctx) { 232 | this.__root = pattern; 233 | this.__ctx = ctx; 234 | }; 235 | 236 | /** 237 | * The mock canvas context 238 | * @param o - options include: 239 | * ctx - existing Context2D to wrap around 240 | * width - width of your canvas (defaults to 500) 241 | * height - height of your canvas (defaults to 500) 242 | * enableMirroring - enables canvas mirroring (get image data) (defaults to false) 243 | * document - the document object (defaults to the current document) 244 | */ 245 | ctx = function (o) { 246 | var defaultOptions = { 247 | width: 500, 248 | height: 500, 249 | veiwBoxWidth: 500, 250 | veiwBoxHeight: 500, 251 | enableMirroring: false, 252 | }, 253 | options; 254 | 255 | //keep support for this way of calling C2S: new C2S(width,height) 256 | if (arguments.length > 1) { 257 | options = defaultOptions; 258 | options.width = arguments[0]; 259 | options.height = arguments[1]; 260 | options.veiwBoxWidth = arguments[2]; 261 | options.veiwBoxHeight = arguments[3]; 262 | } else if (!o) { 263 | options = defaultOptions; 264 | } else { 265 | options = o; 266 | } 267 | 268 | if (!(this instanceof ctx)) { 269 | //did someone call this without new? 270 | return new ctx(options); 271 | } 272 | 273 | //setup options 274 | this.width = options.width || defaultOptions.width; 275 | this.height = options.height || defaultOptions.height; 276 | this.veiwBoxWidth = options.veiwBoxWidth || this.width; 277 | this.veiwBoxHeight = options.veiwBoxHeight || this.height; 278 | this.enableMirroring = 279 | options.enableMirroring !== undefined 280 | ? options.enableMirroring 281 | : defaultOptions.enableMirroring; 282 | 283 | this.canvas = this; ///point back to this instance! 284 | 285 | this.__document = options.document || document; 286 | this.XMLSerializer = options.XMLSerializer || XMLSerializer; 287 | 288 | // console.log(document.createElement); 289 | 290 | // allow passing in an existing context to wrap around 291 | // if a context is passed in, we know a canvas already exist 292 | if (options.ctx) { 293 | this.__ctx = options.ctx; 294 | } else { 295 | this.__canvas = this.__document.createElement("canvas"); 296 | this.__ctx = this.__canvas.getContext("2d"); 297 | } 298 | 299 | this.__setDefaultStyles(); 300 | this.__stack = [this.__getStyleState()]; 301 | this.__groupStack = []; 302 | 303 | //the root svg element 304 | this.__root = this.__document.createElementNS( 305 | "http://www.w3.org/2000/svg", 306 | "svg" 307 | ); 308 | this.__root.setAttribute("version", 1.1); 309 | this.__root.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 310 | // this.__root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", 311 | // "http://www.w3.org/1999/xlink"); 312 | this.__root.setAttribute("width", this.width); 313 | this.__root.setAttribute("height", this.height); 314 | this.__root.setAttribute( 315 | "viewBox", 316 | "0 0 " + this.veiwBoxWidth + " " + this.veiwBoxHeight 317 | ); 318 | //make sure we don't generate the same ids in defs 319 | this.__ids = {}; 320 | 321 | //defs tag 322 | this.__defs = this.__document.createElementNS( 323 | "http://www.w3.org/2000/svg", 324 | "defs" 325 | ); 326 | this.__root.appendChild(this.__defs); 327 | 328 | //also add a group child. the svg element can't use the transform attribute 329 | this.__currentElement = this.__document.createElementNS( 330 | "http://www.w3.org/2000/svg", 331 | "g" 332 | ); 333 | this.__root.appendChild(this.__currentElement); 334 | }; 335 | 336 | /** 337 | * Creates the specified svg element 338 | * @private 339 | */ 340 | ctx.prototype.__createElement = function ( 341 | elementName, 342 | properties, 343 | resetFill 344 | ) { 345 | if (typeof properties === "undefined") { 346 | properties = {}; 347 | } 348 | 349 | var element = this.__document.createElementNS( 350 | "http://www.w3.org/2000/svg", 351 | elementName 352 | ), 353 | keys = Object.keys(properties), 354 | i, 355 | key; 356 | if (resetFill) { 357 | //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black. 358 | element.setAttribute("fill", "none"); 359 | element.setAttribute("stroke", "none"); 360 | } 361 | for (i = 0; i < keys.length; i++) { 362 | key = keys[i]; 363 | element.setAttribute(key, properties[key]); 364 | } 365 | return element; 366 | }; 367 | 368 | /** 369 | * Applies default canvas styles to the context 370 | * @private 371 | */ 372 | ctx.prototype.__setDefaultStyles = function () { 373 | //default 2d canvas context properties see:http://www.w3.org/TR/2dcontext/ 374 | var keys = Object.keys(STYLES), 375 | i, 376 | key; 377 | for (i = 0; i < keys.length; i++) { 378 | key = keys[i]; 379 | this[key] = STYLES[key].canvas; 380 | } 381 | }; 382 | 383 | /** 384 | * Applies styles on restore 385 | * @param styleState 386 | * @private 387 | */ 388 | ctx.prototype.__applyStyleState = function (styleState) { 389 | var keys = Object.keys(styleState), 390 | i, 391 | key; 392 | for (i = 0; i < keys.length; i++) { 393 | key = keys[i]; 394 | this[key] = styleState[key]; 395 | } 396 | }; 397 | 398 | /** 399 | * Gets the current style state 400 | * @return {Object} 401 | * @private 402 | */ 403 | ctx.prototype.__getStyleState = function () { 404 | var i, 405 | styleState = {}, 406 | keys = Object.keys(STYLES), 407 | key; 408 | for (i = 0; i < keys.length; i++) { 409 | key = keys[i]; 410 | styleState[key] = this[key]; 411 | } 412 | return styleState; 413 | }; 414 | 415 | /** 416 | * Apples the current styles to the current SVG element. On "ctx.fill" or "ctx.stroke" 417 | * @param type 418 | * @private 419 | */ 420 | ctx.prototype.__applyStyleToCurrentElement = function (type) { 421 | var currentElement = this.__currentElement; 422 | var currentStyleGroup = this.__currentElementsToStyle; 423 | if (currentStyleGroup) { 424 | currentElement.setAttribute(type, ""); 425 | currentElement = currentStyleGroup.element; 426 | currentStyleGroup.children.forEach(function (node) { 427 | node.setAttribute(type, ""); 428 | }); 429 | } 430 | 431 | var keys = Object.keys(STYLES), 432 | i, 433 | style, 434 | value, 435 | id, 436 | regex, 437 | matches; 438 | for (i = 0; i < keys.length; i++) { 439 | style = STYLES[keys[i]]; 440 | value = this[keys[i]]; 441 | if (style.apply) { 442 | //is this a gradient or pattern? 443 | if (value instanceof CanvasPattern) { 444 | //pattern 445 | if (value.__ctx) { 446 | //copy over defs 447 | while (value.__ctx.__defs.childNodes.length) { 448 | id = value.__ctx.__defs.childNodes[0].getAttribute("id"); 449 | this.__ids[id] = id; 450 | this.__defs.appendChild(value.__ctx.__defs.childNodes[0]); 451 | } 452 | } 453 | currentElement.setAttribute( 454 | style.apply, 455 | format("url(#{id})", { 456 | id: value.__root.getAttribute("id"), 457 | }) 458 | ); 459 | } else if (value instanceof CanvasGradient) { 460 | //gradient 461 | currentElement.setAttribute( 462 | style.apply, 463 | format("url(#{id})", { 464 | id: value.__root.getAttribute("id"), 465 | }) 466 | ); 467 | } else if (style.apply.indexOf(type) !== -1 && style.svg !== value) { 468 | if ( 469 | (style.svgAttr === "stroke" || style.svgAttr === "fill") && 470 | value.indexOf("rgba") !== -1 471 | ) { 472 | //separate alpha value, since illustrator can't handle it 473 | regex = 474 | /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi; 475 | matches = regex.exec(value); 476 | currentElement.setAttribute( 477 | style.svgAttr, 478 | format("rgb({r},{g},{b})", { 479 | r: matches[1], 480 | g: matches[2], 481 | b: matches[3], 482 | }) 483 | ); 484 | //should take globalAlpha here 485 | var opacity = matches[4]; 486 | var globalAlpha = this.globalAlpha; 487 | if (globalAlpha != null) { 488 | opacity *= globalAlpha; 489 | } 490 | currentElement.setAttribute(style.svgAttr + "-opacity", opacity); 491 | } else { 492 | var attr = style.svgAttr; 493 | if (keys[i] === "globalAlpha") { 494 | attr = type + "-" + style.svgAttr; 495 | if (currentElement.getAttribute(attr)) { 496 | //fill-opacity or stroke-opacity has already been set by stroke or fill. 497 | continue; 498 | } 499 | } 500 | //otherwise only update attribute if right type, and not svg default 501 | currentElement.setAttribute(attr, value); 502 | } 503 | } 504 | } 505 | } 506 | }; 507 | 508 | /** 509 | * Will return the closest group or svg node. May return the current element. 510 | * @private 511 | */ 512 | ctx.prototype.__closestGroupOrSvg = function (node) { 513 | node = node || this.__currentElement; 514 | if (node.nodeName === "g" || node.nodeName === "svg") { 515 | return node; 516 | } else { 517 | return this.__closestGroupOrSvg(node.parentNode); 518 | } 519 | }; 520 | 521 | /** 522 | * Returns the serialized value of the svg so far 523 | * @param fixNamedEntities - Standalone SVG doesn't support named entities, which document.createTextNode encodes. 524 | * If true, we attempt to find all named entities and encode it as a numeric entity. 525 | * @return serialized svg 526 | */ 527 | ctx.prototype.getSerializedSvg = function (fixNamedEntities) { 528 | var serialized = new this.XMLSerializer().serializeToString(this.__root), 529 | keys, 530 | i, 531 | key, 532 | value, 533 | regexp, 534 | xmlns; 535 | 536 | //IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly 537 | xmlns = 538 | /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; 539 | 540 | if (xmlns.test(serialized)) { 541 | serialized = serialized.replace( 542 | 'xmlns="http://www.w3.org/2000/svg', 543 | 'xmlns:xlink="http://www.w3.org/1999/xlink' 544 | ); 545 | } 546 | 547 | if (fixNamedEntities) { 548 | keys = Object.keys(namedEntities); 549 | //loop over each named entity and replace with the proper equivalent. 550 | for (i = 0; i < keys.length; i++) { 551 | key = keys[i]; 552 | value = namedEntities[key]; 553 | regexp = new RegExp(key, "gi"); 554 | if (regexp.test(serialized)) { 555 | serialized = serialized.replace(regexp, value); 556 | } 557 | } 558 | } 559 | 560 | return serialized; 561 | }; 562 | 563 | /** 564 | * Returns the root svg 565 | * @return 566 | */ 567 | ctx.prototype.getSvg = function () { 568 | return this.__root; 569 | }; 570 | /** 571 | * Will generate a group tag. 572 | */ 573 | ctx.prototype.save = function () { 574 | var group = this.__createElement("g"); 575 | var parent = this.__closestGroupOrSvg(); 576 | this.__groupStack.push(parent); 577 | parent.appendChild(group); 578 | this.__currentElement = group; 579 | this.__stack.push(this.__getStyleState()); 580 | }; 581 | /** 582 | * Sets current element to parent, or just root if already root 583 | */ 584 | ctx.prototype.restore = function () { 585 | this.__currentElement = this.__groupStack.pop(); 586 | this.__currentElementsToStyle = null; 587 | //Clearing canvas will make the poped group invalid, currentElement is set to the root group node. 588 | if (!this.__currentElement) { 589 | this.__currentElement = this.__root.childNodes[1]; 590 | } 591 | var state = this.__stack.pop(); 592 | this.__applyStyleState(state); 593 | }; 594 | 595 | /** 596 | * Helper method to add transform 597 | * @private 598 | */ 599 | ctx.prototype.__addTransform = function (t) { 600 | //if the current element has siblings, add another group 601 | var parent = this.__closestGroupOrSvg(); 602 | if (parent.childNodes.length > 0) { 603 | if (this.__currentElement.nodeName === "path") { 604 | if (!this.__currentElementsToStyle) 605 | this.__currentElementsToStyle = { 606 | element: parent, 607 | children: [], 608 | }; 609 | this.__currentElementsToStyle.children.push(this.__currentElement); 610 | this.__applyCurrentDefaultPath(); 611 | } 612 | 613 | var group = this.__createElement("g"); 614 | parent.appendChild(group); 615 | this.__currentElement = group; 616 | } 617 | 618 | var transform = this.__currentElement.getAttribute("transform"); 619 | if (transform) { 620 | transform += " "; 621 | } else { 622 | transform = ""; 623 | } 624 | transform += t; 625 | this.__currentElement.setAttribute("transform", transform); 626 | }; 627 | 628 | /** 629 | * scales the current element 630 | */ 631 | ctx.prototype.scale = function (x, y) { 632 | if (y === undefined) { 633 | y = x; 634 | } 635 | this.__addTransform( 636 | format("scale({x},{y})", { 637 | x: x, 638 | y: y, 639 | }) 640 | ); 641 | }; 642 | 643 | /** 644 | * rotates the current element 645 | */ 646 | ctx.prototype.rotate = function (angle) { 647 | var degrees = (angle * 180) / Math.PI; 648 | this.__addTransform( 649 | format("rotate({angle},{cx},{cy})", { 650 | angle: degrees, 651 | cx: 0, 652 | cy: 0, 653 | }) 654 | ); 655 | }; 656 | 657 | /** 658 | * translates the current element 659 | */ 660 | ctx.prototype.translate = function (x, y) { 661 | this.__addTransform( 662 | format("translate({x},{y})", { 663 | x: x, 664 | y: y, 665 | }) 666 | ); 667 | }; 668 | 669 | /** 670 | * applies a transform to the current element 671 | */ 672 | ctx.prototype.transform = function (a, b, c, d, e, f) { 673 | this.__addTransform( 674 | format("matrix({a},{b},{c},{d},{e},{f})", { 675 | a: a, 676 | b: b, 677 | c: c, 678 | d: d, 679 | e: e, 680 | f: f, 681 | }) 682 | ); 683 | }; 684 | 685 | /** 686 | * Create a new Path Element 687 | */ 688 | ctx.prototype.beginPath = function () { 689 | var path, parent; 690 | 691 | // Note that there is only one current default path, it is not part of the drawing state. 692 | // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path 693 | this.__currentDefaultPath = ""; 694 | this.__currentPosition = {}; 695 | 696 | path = this.__createElement("path", {}, true); 697 | parent = this.__closestGroupOrSvg(); 698 | parent.appendChild(path); 699 | this.__currentElement = path; 700 | }; 701 | 702 | /** 703 | * Helper function to apply currentDefaultPath to current path element 704 | * @private 705 | */ 706 | ctx.prototype.__applyCurrentDefaultPath = function () { 707 | var currentElement = this.__currentElement; 708 | if (currentElement.nodeName === "path") { 709 | currentElement.setAttribute("d", this.__currentDefaultPath); 710 | } else { 711 | console.error( 712 | "Attempted to apply path command to node", 713 | currentElement.nodeName 714 | ); 715 | } 716 | }; 717 | 718 | /** 719 | * Helper function to add path command 720 | * @private 721 | */ 722 | ctx.prototype.__addPathCommand = function (command) { 723 | this.__currentDefaultPath += " "; 724 | this.__currentDefaultPath += command; 725 | }; 726 | 727 | /** 728 | * Adds the move command to the current path element, 729 | * if the currentPathElement is not empty create a new path element 730 | */ 731 | ctx.prototype.moveTo = function (x, y) { 732 | if (this.__currentElement.nodeName !== "path") { 733 | this.beginPath(); 734 | } 735 | 736 | // creates a new subpath with the given point 737 | this.__currentPosition = { 738 | x: x, 739 | y: y, 740 | }; 741 | this.__addPathCommand( 742 | format("M {x} {y}", { 743 | x: x, 744 | y: y, 745 | }) 746 | ); 747 | }; 748 | 749 | /** 750 | * Closes the current path 751 | */ 752 | ctx.prototype.closePath = function () { 753 | if (this.__currentDefaultPath) { 754 | this.__addPathCommand("Z"); 755 | } 756 | }; 757 | 758 | /** 759 | * Adds a line to command 760 | */ 761 | ctx.prototype.lineTo = function (x, y) { 762 | this.__currentPosition = { 763 | x: x, 764 | y: y, 765 | }; 766 | if (this.__currentDefaultPath.indexOf("M") > -1) { 767 | this.__addPathCommand( 768 | format("L {x} {y}", { 769 | x: x, 770 | y: y, 771 | }) 772 | ); 773 | } else { 774 | this.__addPathCommand( 775 | format("M {x} {y}", { 776 | x: x, 777 | y: y, 778 | }) 779 | ); 780 | } 781 | }; 782 | 783 | /** 784 | * Add a bezier command 785 | */ 786 | ctx.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { 787 | this.__currentPosition = { 788 | x: x, 789 | y: y, 790 | }; 791 | this.__addPathCommand( 792 | format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", { 793 | cp1x: cp1x, 794 | cp1y: cp1y, 795 | cp2x: cp2x, 796 | cp2y: cp2y, 797 | x: x, 798 | y: y, 799 | }) 800 | ); 801 | }; 802 | 803 | /** 804 | * Adds a quadratic curve to command 805 | */ 806 | ctx.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { 807 | this.__currentPosition = { 808 | x: x, 809 | y: y, 810 | }; 811 | this.__addPathCommand( 812 | format("Q {cpx} {cpy} {x} {y}", { 813 | cpx: cpx, 814 | cpy: cpy, 815 | x: x, 816 | y: y, 817 | }) 818 | ); 819 | }; 820 | 821 | /** 822 | * Return a new normalized vector of given vector 823 | */ 824 | var normalize = function (vector) { 825 | var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); 826 | return [vector[0] / len, vector[1] / len]; 827 | }; 828 | 829 | /** 830 | * Adds the arcTo to the current path 831 | * 832 | * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto 833 | */ 834 | ctx.prototype.arcTo = function (x1, y1, x2, y2, radius) { 835 | // Let the point (x0, y0) be the last point in the subpath. 836 | var x0 = this.__currentPosition && this.__currentPosition.x; 837 | var y0 = this.__currentPosition && this.__currentPosition.y; 838 | 839 | // First ensure there is a subpath for (x1, y1). 840 | if (typeof x0 == "undefined" || typeof y0 == "undefined") { 841 | return; 842 | } 843 | 844 | // Negative values for radius must cause the implementation to throw an IndexSizeError exception. 845 | if (radius < 0) { 846 | throw new Error( 847 | "IndexSizeError: The radius provided (" + radius + ") is negative." 848 | ); 849 | } 850 | 851 | // If the point (x0, y0) is equal to the point (x1, y1), 852 | // or if the point (x1, y1) is equal to the point (x2, y2), 853 | // or if the radius radius is zero, 854 | // then the method must add the point (x1, y1) to the subpath, 855 | // and connect that point to the previous point (x0, y0) by a straight line. 856 | if ((x0 === x1 && y0 === y1) || (x1 === x2 && y1 === y2) || radius === 0) { 857 | this.lineTo(x1, y1); 858 | return; 859 | } 860 | 861 | // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, 862 | // then the method must add the point (x1, y1) to the subpath, 863 | // and connect that point to the previous point (x0, y0) by a straight line. 864 | var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); 865 | var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); 866 | if ( 867 | unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === 868 | unit_vec_p1_p0[1] * unit_vec_p1_p2[0] 869 | ) { 870 | this.lineTo(x1, y1); 871 | return; 872 | } 873 | 874 | // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, 875 | // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), 876 | // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2). 877 | // The points at which this circle touches these two lines are called the start and end tangent points respectively. 878 | 879 | // note that both vectors are unit vectors, so the length is 1 880 | var cos = 881 | unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + 882 | unit_vec_p1_p0[1] * unit_vec_p1_p2[1]; 883 | var theta = Math.acos(Math.abs(cos)); 884 | 885 | // Calculate origin 886 | var unit_vec_p1_origin = normalize([ 887 | unit_vec_p1_p0[0] + unit_vec_p1_p2[0], 888 | unit_vec_p1_p0[1] + unit_vec_p1_p2[1], 889 | ]); 890 | var len_p1_origin = radius / Math.sin(theta / 2); 891 | var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; 892 | var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; 893 | 894 | // Calculate start angle and end angle 895 | // rotate 90deg clockwise (note that y axis points to its down) 896 | var unit_vec_origin_start_tangent = [-unit_vec_p1_p0[1], unit_vec_p1_p0[0]]; 897 | // rotate 90deg counter clockwise (note that y axis points to its down) 898 | var unit_vec_origin_end_tangent = [unit_vec_p1_p2[1], -unit_vec_p1_p2[0]]; 899 | var getAngle = function (vector) { 900 | // get angle (clockwise) between vector and (1, 0) 901 | var x = vector[0]; 902 | var y = vector[1]; 903 | if (y >= 0) { 904 | // note that y axis points to its down 905 | return Math.acos(x); 906 | } else { 907 | return -Math.acos(x); 908 | } 909 | }; 910 | var startAngle = getAngle(unit_vec_origin_start_tangent); 911 | var endAngle = getAngle(unit_vec_origin_end_tangent); 912 | 913 | // Connect the point (x0, y0) to the start tangent point by a straight line 914 | this.lineTo( 915 | x + unit_vec_origin_start_tangent[0] * radius, 916 | y + unit_vec_origin_start_tangent[1] * radius 917 | ); 918 | 919 | // Connect the start tangent point to the end tangent point by arc 920 | // and adding the end tangent point to the subpath. 921 | this.arc(x, y, radius, startAngle, endAngle); 922 | }; 923 | 924 | /** 925 | * Sets the stroke property on the current element 926 | */ 927 | ctx.prototype.stroke = function () { 928 | if (this.__currentElement.nodeName === "path") { 929 | this.__currentElement.setAttribute("paint-order", "fill stroke markers"); 930 | } 931 | this.__applyCurrentDefaultPath(); 932 | this.__applyStyleToCurrentElement("stroke"); 933 | }; 934 | 935 | /** 936 | * Sets fill properties on the current element 937 | */ 938 | ctx.prototype.fill = function () { 939 | if (this.__currentElement.nodeName === "path") { 940 | this.__currentElement.setAttribute("paint-order", "stroke fill markers"); 941 | } 942 | this.__applyCurrentDefaultPath(); 943 | this.__applyStyleToCurrentElement("fill"); 944 | }; 945 | 946 | /** 947 | * Adds a rectangle to the path. 948 | */ 949 | ctx.prototype.rect = function (x, y, width, height) { 950 | if (this.__currentElement.nodeName !== "path") { 951 | this.beginPath(); 952 | } 953 | this.moveTo(x, y); 954 | this.lineTo(x + width, y); 955 | this.lineTo(x + width, y + height); 956 | this.lineTo(x, y + height); 957 | this.lineTo(x, y); 958 | this.closePath(); 959 | }; 960 | 961 | /** 962 | * adds a rectangle element 963 | */ 964 | ctx.prototype.fillRect = function (x, y, width, height) { 965 | var rect, parent; 966 | rect = this.__createElement( 967 | "rect", 968 | { 969 | x: x, 970 | y: y, 971 | width: width, 972 | height: height, 973 | }, 974 | true 975 | ); 976 | parent = this.__closestGroupOrSvg(); 977 | parent.appendChild(rect); 978 | this.__currentElement = rect; 979 | this.__applyStyleToCurrentElement("fill"); 980 | }; 981 | 982 | /** 983 | * Draws a rectangle with no fill 984 | * @param x 985 | * @param y 986 | * @param width 987 | * @param height 988 | */ 989 | ctx.prototype.strokeRect = function (x, y, width, height) { 990 | var rect, parent; 991 | rect = this.__createElement( 992 | "rect", 993 | { 994 | x: x, 995 | y: y, 996 | width: width, 997 | height: height, 998 | }, 999 | true 1000 | ); 1001 | parent = this.__closestGroupOrSvg(); 1002 | parent.appendChild(rect); 1003 | this.__currentElement = rect; 1004 | this.__applyStyleToCurrentElement("stroke"); 1005 | }; 1006 | 1007 | /** 1008 | * Clear entire canvas: 1009 | * 1. save current transforms 1010 | * 2. remove all the childNodes of the root g element 1011 | */ 1012 | ctx.prototype.__clearCanvas = function () { 1013 | var current = this.__closestGroupOrSvg(), 1014 | transform = current.getAttribute("transform"); 1015 | var rootGroup = this.__root.childNodes[1]; 1016 | var childNodes = rootGroup.childNodes; 1017 | for (var i = childNodes.length - 1; i >= 0; i--) { 1018 | if (childNodes[i]) { 1019 | rootGroup.removeChild(childNodes[i]); 1020 | } 1021 | } 1022 | this.__currentElement = rootGroup; 1023 | //reset __groupStack as all the child group nodes are all removed. 1024 | this.__groupStack = []; 1025 | if (transform) { 1026 | this.__addTransform(transform); 1027 | } 1028 | }; 1029 | 1030 | /** 1031 | * "Clears" a canvas by just drawing a white rectangle in the current group. 1032 | */ 1033 | ctx.prototype.clearRect = function (x, y, width, height) { 1034 | //clear entire canvas 1035 | if (x === 0 && y === 0 && width === this.width && height === this.height) { 1036 | this.__clearCanvas(); 1037 | return; 1038 | } 1039 | var rect, 1040 | parent = this.__closestGroupOrSvg(); 1041 | rect = this.__createElement( 1042 | "rect", 1043 | { 1044 | x: x, 1045 | y: y, 1046 | width: width, 1047 | height: height, 1048 | fill: "#FFFFFF", 1049 | }, 1050 | true 1051 | ); 1052 | parent.appendChild(rect); 1053 | }; 1054 | 1055 | /** 1056 | * Adds a linear gradient to a defs tag. 1057 | * Returns a canvas gradient object that has a reference to it's parent def 1058 | */ 1059 | ctx.prototype.createLinearGradient = function (x1, y1, x2, y2) { 1060 | var grad = this.__createElement( 1061 | "linearGradient", 1062 | { 1063 | id: randomString(this.__ids), 1064 | x1: x1 + "px", 1065 | x2: x2 + "px", 1066 | y1: y1 + "px", 1067 | y2: y2 + "px", 1068 | gradientUnits: "userSpaceOnUse", 1069 | }, 1070 | false 1071 | ); 1072 | this.__defs.appendChild(grad); 1073 | return new CanvasGradient(grad, this); 1074 | }; 1075 | 1076 | /** 1077 | * Adds a radial gradient to a defs tag. 1078 | * Returns a canvas gradient object that has a reference to it's parent def 1079 | */ 1080 | ctx.prototype.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { 1081 | var grad = this.__createElement( 1082 | "radialGradient", 1083 | { 1084 | id: randomString(this.__ids), 1085 | cx: x1 + "px", 1086 | cy: y1 + "px", 1087 | r: r1 + "px", 1088 | fx: x0 + "px", 1089 | fy: y0 + "px", 1090 | gradientUnits: "userSpaceOnUse", 1091 | }, 1092 | false 1093 | ); 1094 | this.__defs.appendChild(grad); 1095 | return new CanvasGradient(grad, this); 1096 | }; 1097 | 1098 | /** 1099 | * Parses the font string and returns svg mapping 1100 | * @private 1101 | */ 1102 | ctx.prototype.__parseFont = function () { 1103 | var regex = 1104 | /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\'\"\sa-z0-9]+?)\s*$/i; 1105 | var fontPart = regex.exec(this.font); 1106 | var data = { 1107 | style: fontPart[1] || "normal", 1108 | size: fontPart[4] || "10px", 1109 | family: fontPart[6] || "sans-serif", 1110 | weight: fontPart[3] || "normal", 1111 | decoration: fontPart[2] || "normal", 1112 | href: null, 1113 | }; 1114 | 1115 | //canvas doesn't support underline natively, but we can pass this attribute 1116 | if (this.__fontUnderline === "underline") { 1117 | data.decoration = "underline"; 1118 | } 1119 | 1120 | //canvas also doesn't support linking, but we can pass this as well 1121 | if (this.__fontHref) { 1122 | data.href = this.__fontHref; 1123 | } 1124 | 1125 | return data; 1126 | }; 1127 | 1128 | /** 1129 | * Helper to link text fragments 1130 | * @param font 1131 | * @param element 1132 | * @return {*} 1133 | * @private 1134 | */ 1135 | ctx.prototype.__wrapTextLink = function (font, element) { 1136 | if (font.href) { 1137 | var a = this.__createElement("a"); 1138 | a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", font.href); 1139 | a.appendChild(element); 1140 | return a; 1141 | } 1142 | return element; 1143 | }; 1144 | 1145 | /** 1146 | * Fills or strokes text 1147 | * @param text 1148 | * @param x 1149 | * @param y 1150 | * @param action - stroke or fill 1151 | * @private 1152 | */ 1153 | ctx.prototype.__applyText = function (text, x, y, action) { 1154 | var font = this.__parseFont(), 1155 | parent = this.__closestGroupOrSvg(), 1156 | textElement = this.__createElement( 1157 | "text", 1158 | { 1159 | "font-family": font.family, 1160 | "font-size": font.size, 1161 | "font-style": font.style, 1162 | "font-weight": font.weight, 1163 | "text-decoration": font.decoration, 1164 | x: x, 1165 | y: y, 1166 | "text-anchor": getTextAnchor(this.textAlign), 1167 | "dominant-baseline": getDominantBaseline(this.textBaseline), 1168 | }, 1169 | true 1170 | ); 1171 | 1172 | textElement.appendChild(this.__document.createTextNode(text)); 1173 | this.__currentElement = textElement; 1174 | this.__applyStyleToCurrentElement(action); 1175 | parent.appendChild(this.__wrapTextLink(font, textElement)); 1176 | }; 1177 | 1178 | /** 1179 | * Creates a text element 1180 | * @param text 1181 | * @param x 1182 | * @param y 1183 | */ 1184 | ctx.prototype.fillText = function (text, x, y) { 1185 | this.__applyText(text, x, y, "fill"); 1186 | }; 1187 | 1188 | /** 1189 | * Strokes text 1190 | * @param text 1191 | * @param x 1192 | * @param y 1193 | */ 1194 | ctx.prototype.strokeText = function (text, x, y) { 1195 | this.__applyText(text, x, y, "stroke"); 1196 | }; 1197 | 1198 | /** 1199 | * No need to implement this for svg. 1200 | * @param text 1201 | * @return {TextMetrics} 1202 | */ 1203 | ctx.prototype.measureText = function (text) { 1204 | this.__ctx.font = this.font; 1205 | return this.__ctx.measureText(text); 1206 | }; 1207 | 1208 | /** 1209 | * Arc command! 1210 | */ 1211 | ctx.prototype.arc = function ( 1212 | x, 1213 | y, 1214 | radius, 1215 | startAngle, 1216 | endAngle, 1217 | counterClockwise 1218 | ) { 1219 | // in canvas no circle is drawn if no angle is provided. 1220 | if (startAngle === endAngle) { 1221 | return; 1222 | } 1223 | startAngle = startAngle % (2 * Math.PI); 1224 | endAngle = endAngle % (2 * Math.PI); 1225 | if (startAngle === endAngle) { 1226 | //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) 1227 | endAngle = 1228 | (endAngle + 2 * Math.PI - 0.001 * (counterClockwise ? -1 : 1)) % 1229 | (2 * Math.PI); 1230 | } 1231 | var endX = x + radius * Math.cos(endAngle), 1232 | endY = y + radius * Math.sin(endAngle), 1233 | startX = x + radius * Math.cos(startAngle), 1234 | startY = y + radius * Math.sin(startAngle), 1235 | sweepFlag = counterClockwise ? 0 : 1, 1236 | largeArcFlag = 0, 1237 | diff = endAngle - startAngle; 1238 | 1239 | // https://github.com/gliffy/canvas2svg/issues/4 1240 | if (diff < 0) { 1241 | diff += 2 * Math.PI; 1242 | } 1243 | 1244 | if (counterClockwise) { 1245 | largeArcFlag = diff > Math.PI ? 0 : 1; 1246 | } else { 1247 | largeArcFlag = diff > Math.PI ? 1 : 0; 1248 | } 1249 | 1250 | this.lineTo(startX, startY); 1251 | this.__addPathCommand( 1252 | format( 1253 | "A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", 1254 | { 1255 | rx: radius, 1256 | ry: radius, 1257 | xAxisRotation: 0, 1258 | largeArcFlag: largeArcFlag, 1259 | sweepFlag: sweepFlag, 1260 | endX: endX, 1261 | endY: endY, 1262 | } 1263 | ) 1264 | ); 1265 | 1266 | this.__currentPosition = { 1267 | x: endX, 1268 | y: endY, 1269 | }; 1270 | }; 1271 | 1272 | /** 1273 | * Generates a ClipPath from the clip command. 1274 | */ 1275 | ctx.prototype.clip = function () { 1276 | var group = this.__closestGroupOrSvg(), 1277 | clipPath = this.__createElement("clipPath"), 1278 | id = randomString(this.__ids), 1279 | newGroup = this.__createElement("g"); 1280 | 1281 | this.__applyCurrentDefaultPath(); 1282 | group.removeChild(this.__currentElement); 1283 | clipPath.setAttribute("id", id); 1284 | clipPath.appendChild(this.__currentElement); 1285 | 1286 | this.__defs.appendChild(clipPath); 1287 | 1288 | //set the clip path to this group 1289 | group.setAttribute( 1290 | "clip-path", 1291 | format("url(#{id})", { 1292 | id: id, 1293 | }) 1294 | ); 1295 | 1296 | //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations 1297 | // to this path 1298 | group.appendChild(newGroup); 1299 | 1300 | this.__currentElement = newGroup; 1301 | }; 1302 | 1303 | /** 1304 | * Draws a canvas, image or mock context to this canvas. 1305 | * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support. 1306 | * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage 1307 | */ 1308 | ctx.prototype.drawImage = function () { 1309 | //convert arguments to a real array 1310 | var args = Array.prototype.slice.call(arguments), 1311 | image = args[0], 1312 | dx, 1313 | dy, 1314 | dw, 1315 | dh, 1316 | sx = 0, 1317 | sy = 0, 1318 | sw, 1319 | sh, 1320 | parent, 1321 | svg, 1322 | defs, 1323 | group, 1324 | currentElement, 1325 | svgImage, 1326 | canvas, 1327 | context, 1328 | id; 1329 | 1330 | if (args.length === 3) { 1331 | dx = args[1]; 1332 | dy = args[2]; 1333 | sw = image.width; 1334 | sh = image.height; 1335 | dw = sw; 1336 | dh = sh; 1337 | } else if (args.length === 5) { 1338 | dx = args[1]; 1339 | dy = args[2]; 1340 | dw = args[3]; 1341 | dh = args[4]; 1342 | sw = image.width; 1343 | sh = image.height; 1344 | } else if (args.length === 9) { 1345 | sx = args[1]; 1346 | sy = args[2]; 1347 | sw = args[3]; 1348 | sh = args[4]; 1349 | dx = args[5]; 1350 | dy = args[6]; 1351 | dw = args[7]; 1352 | dh = args[8]; 1353 | } else { 1354 | throw new Error( 1355 | "Invalid number of arguments passed to drawImage: " + arguments.length 1356 | ); 1357 | } 1358 | 1359 | parent = this.__closestGroupOrSvg(); 1360 | currentElement = this.__currentElement; 1361 | var translateDirective = "translate(" + dx + ", " + dy + ")"; 1362 | //canvas image 1363 | svgImage = this.__createElement("image"); 1364 | svgImage.setAttribute("width", dw); 1365 | svgImage.setAttribute("height", dh); 1366 | svgImage.setAttribute("preserveAspectRatio", "none"); 1367 | svgImage.setAttribute("opacity", this.globalAlpha); 1368 | if (sx || sy || sw !== image.width || sh !== image.height) { 1369 | //crop the image using a temporary canvas 1370 | canvas = this.__document.createElement("canvas"); 1371 | canvas.width = dw; 1372 | canvas.height = dh; 1373 | context = canvas.getContext("2d"); 1374 | context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); 1375 | image = canvas; 1376 | } 1377 | 1378 | svgImage.setAttribute("transform", translateDirective); 1379 | svgImage.setAttributeNS( 1380 | "http://www.w3.org/1999/xlink", 1381 | "xlink:href", 1382 | image.nodeName === "CANVAS" ? image.toDataURL() : image.originalSrc 1383 | ); 1384 | parent.appendChild(svgImage); 1385 | }; 1386 | 1387 | /** 1388 | * Generates a pattern tag 1389 | */ 1390 | ctx.prototype.createPattern = function (image, repetition) { 1391 | var pattern = this.__document.createElementNS( 1392 | "http://www.w3.org/2000/svg", 1393 | "pattern" 1394 | ), 1395 | id = randomString(this.__ids), 1396 | img; 1397 | pattern.setAttribute("id", id); 1398 | pattern.setAttribute("width", image.width); 1399 | pattern.setAttribute("height", image.height); 1400 | if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { 1401 | img = this.__document.createElementNS( 1402 | "http://www.w3.org/2000/svg", 1403 | "image" 1404 | ); 1405 | img.setAttribute("width", image.width); 1406 | img.setAttribute("height", image.height); 1407 | img.setAttributeNS( 1408 | "http://www.w3.org/1999/xlink", 1409 | "xlink:href", 1410 | image.nodeName === "CANVAS" 1411 | ? image.toDataURL() 1412 | : image.getAttribute("src") 1413 | ); 1414 | pattern.appendChild(img); 1415 | this.__defs.appendChild(pattern); 1416 | } else if (image instanceof ctx) { 1417 | pattern.appendChild(image.__root.childNodes[1]); 1418 | this.__defs.appendChild(pattern); 1419 | } 1420 | return new CanvasPattern(pattern, this); 1421 | }; 1422 | 1423 | ctx.prototype.setLineDash = function (dashArray) { 1424 | if (dashArray && dashArray.length > 0) { 1425 | this.lineDash = dashArray.join(","); 1426 | } else { 1427 | this.lineDash = null; 1428 | } 1429 | }; 1430 | 1431 | /** 1432 | * Not yet implemented 1433 | */ 1434 | ctx.prototype.drawFocusRing = function () {}; 1435 | ctx.prototype.createImageData = function () {}; 1436 | ctx.prototype.getImageData = function () {}; 1437 | ctx.prototype.putImageData = function () {}; 1438 | ctx.prototype.globalCompositeOperation = function () {}; 1439 | ctx.prototype.setTransform = function () {}; 1440 | 1441 | //add options for alternative namespace 1442 | if (typeof window === "object") { 1443 | window.C2S = ctx; 1444 | } 1445 | 1446 | // CommonJS/Browserify 1447 | if (typeof module === "object" && typeof module.exports === "object") { 1448 | module.exports = ctx; 1449 | } 1450 | })(); 1451 | -------------------------------------------------------------------------------- /demo/avatars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/avatars.png -------------------------------------------------------------------------------- /demo/demo_node.js: -------------------------------------------------------------------------------- 1 | const QRCode = require("../index"); 2 | 3 | const fs = require("fs"); 4 | 5 | // ================================ PNG Configs 6 | 7 | var config = { 8 | // ====== Basic 9 | text: "www.easyproject.cn/donation", 10 | width: 256, 11 | height: 256, 12 | quietZone: 0, 13 | colorDark: "#000000", 14 | colorLight: "#ffffff", 15 | correctLevel: QRCode.CorrectLevel.H, // L, M, Q, H 16 | dotScale: 1, // Must be greater than 0, less than or equal to 1. default is 1 17 | }; 18 | 19 | var config2 = { 20 | // ====== Basic 21 | text: "www.easyproject.cn/donation", 22 | 23 | width: 400, 24 | height: 400, 25 | quietZone: 0, 26 | correctLevel: QRCode.CorrectLevel.H, // L, M, Q, H 27 | dotScale: 0.5, // Must be greater than 0, less than or equal to 1. default is 1 28 | colorDark: "#473C8B", 29 | colorLight: "#FFFACD", 30 | 31 | // === Posotion Pattern(Eye) Color 32 | PI: "#BF3030", 33 | PO: "#269926", 34 | 35 | PI_TL: "#b7d28d", // Position Inner - Top Left 36 | PO_TL: "#aa5b71", // Position Outer - Top Right 37 | AO: "#336699", // Position Outer - Bottom Right 38 | AI: "#336699", // Position Inner - Bottom Right 39 | 40 | // === Aligment color 41 | AI: "#009ACD", 42 | AO: "#B03060", 43 | 44 | // === Timing Pattern Color 45 | // timing: '#e1622f', // Global Timing color. if not set, the defaut is `colorDark` 46 | timing_H: "#ff6600", // Horizontal timing color 47 | timing_V: "#cc0033", // Vertical timing color 48 | 49 | // === Background image 50 | backgroundImage: "logo.png", 51 | backgroundImageAlpha: 0.3, 52 | autoColor: true, 53 | }; 54 | 55 | var config3 = { 56 | // ====== Basic 57 | text: "www.easyproject.cn/donation", 58 | 59 | width: 400, 60 | height: 400, 61 | correctLevel: QRCode.CorrectLevel.H, // L, M, Q, H 62 | dotScale: 0.5, // Must be greater than 0, less than or equal to 1. default is 1 63 | colorDark: "#473C8B", 64 | colorLight: "#FFFACD", 65 | 66 | // === Title 67 | title: "EasyQRCode", // Title 68 | titleFont: "normal normal bold 24px Arial", // Title font 69 | titleColor: "#004284", // Title Color 70 | titleBackgroundColor: "#ffffff", // Title Background 71 | titleHeight: 70, // Title height, include subTitle 72 | titleTop: 25, // Title draw position(Y coordinate), default is 30 73 | 74 | // === SubTitle 75 | subTitle: "nodejs", // Subtitle content 76 | subTitleFont: "normal normal normal 20px Arial", // Subtitle font 77 | subTitleColor: "#269926", // Subtitle color 78 | subTitleTop: 50, // Subtitle drwa position(Y coordinate), default is 50 79 | 80 | // === Posotion Pattern(Eye) Color 81 | PI: "#BF3030", 82 | PO: "#269926", 83 | 84 | PI_TL: "#b7d28d", // Position Inner - Top Left 85 | PO_TL: "#aa5b71", // Position Outer - Top Right 86 | AO: "#336699", // Position Outer - Bottom Right 87 | AI: "#336699", // Position Inner - Bottom Right 88 | 89 | // === Aligment color 90 | AI: "#009ACD", 91 | AO: "#B03060", 92 | 93 | // === Timing Pattern Color 94 | // timing: '#e1622f', // Global Timing color. if not set, the defaut is `colorDark` 95 | timing_H: "#ff6600", // Horizontal timing color 96 | timing_V: "#cc0033", // Vertical timing color 97 | 98 | // === Logo 99 | // logo: "https://avatars1.githubusercontent.com/u/4082017?s=160&v=4", // LOGO 100 | logo: "avatars.png", // LOGO 101 | // logo:"http://127.0.0.1:8020/easy-qrcodejs/demo/logo.png", 102 | // logoWidth:80, 103 | // logoHeight:80, 104 | logoBackgroundColor: "#FFF8DC", // Logo backgroud color, Invalid when `logBgTransparent` is true; default is '#ffffff' 105 | logoBackgroundTransparent: false, // Whether use transparent image, default is false 106 | 107 | // === Background image 108 | backgroundImage: "logo.png", 109 | backgroundImageAlpha: 0.3, 110 | autoColor: true, 111 | 112 | onRenderingStart: function (options) { 113 | console.info(`The QRCode file 'q3.${options.format}' was created.`); 114 | }, 115 | }; 116 | 117 | var config4 = { 118 | // ====== Basic 119 | text: "www.easyproject.cn/donation", 120 | 121 | width: 400, 122 | height: 400, 123 | correctLevel: QRCode.CorrectLevel.H, // L, M, Q, H 124 | dotScale: 0.5, // Must be greater than 0, less than or equal to 1. default is 1 125 | colorDark: "#473C8B", 126 | colorLight: "#FFFACD", 127 | 128 | // QuietZone 129 | quietZone: 15, 130 | quietZoneColor: "#00CED1", 131 | 132 | // === Posotion Pattern(Eye) Color 133 | PI: "#BF3030", 134 | PO: "#269926", 135 | 136 | PI_TL: "#b7d28d", // Position Inner - Top Left 137 | PO_TL: "#aa5b71", // Position Outer - Top Right 138 | AO: "#336699", // Position Outer - Bottom Right 139 | AI: "#336699", // Position Inner - Bottom Right 140 | 141 | // === Aligment color 142 | AI: "#009ACD", 143 | AO: "#B03060", 144 | 145 | // === Timing Pattern Color 146 | // timing: '#e1622f', // Global Timing color. if not set, the defaut is `colorDark` 147 | timing_H: "#ff6600", // Horizontal timing color 148 | timing_V: "#cc0033", // Vertical timing color 149 | 150 | // === Logo 151 | // logo: "https://avatars1.githubusercontent.com/u/4082017?s=160&v=4", // LOGO 152 | logo: "./avatars.png", // LOGO 153 | // logo:"http://127.0.0.1:8020/easy-qrcodejs/demo/logo.png", 154 | // logoWidth:80, 155 | // logoHeight:80, 156 | logoBackgroundColor: "#FFF8DC", // Logo backgroud color, Invalid when `logBgTransparent` is true; default is '#ffffff' 157 | logoBackgroundTransparent: false, // Whether use transparent image, default is false 158 | }; 159 | 160 | // ================================ PNG Test 161 | 162 | var qrcode = new QRCode(config); 163 | var qrcode2 = new QRCode(config2); 164 | var qrcode3 = new QRCode(config3); 165 | var qrcode4 = new QRCode(config4); 166 | 167 | qrcode.saveImage({ 168 | path: "q.png", 169 | }); 170 | qrcode2.saveImage({ 171 | path: "q2.png", 172 | }); 173 | qrcode3.saveImage({ 174 | path: "q3.png", 175 | }); 176 | qrcode4.saveImage({ 177 | path: "q4.png", 178 | }); 179 | qrcode.toDataURL().then((data) => { 180 | console.info("======QRCode PNG DataURL======"); 181 | console.info(data); 182 | console.info(""); 183 | }); 184 | 185 | // ================================ JPG Test 186 | 187 | var config5 = Object.assign({}, config, { 188 | format: "JPG", 189 | version: 6, 190 | }); 191 | var config6 = Object.assign({}, config2, { 192 | format: "JPG", 193 | }); 194 | var config7 = Object.assign({}, config3, { 195 | format: "JPG", 196 | }); 197 | var config8 = Object.assign({}, config4, { 198 | format: "JPG", 199 | }); 200 | 201 | var qrcode5 = new QRCode(config5); 202 | var qrcode6 = new QRCode(config6); 203 | var qrcode7 = new QRCode(config7); 204 | var qrcode8 = new QRCode(config8); 205 | 206 | qrcode5.saveImage({ 207 | path: "q.jpg", 208 | }); 209 | qrcode6.saveImage({ 210 | path: "q2.jpg", 211 | }); 212 | qrcode7.saveImage({ 213 | path: "q3.jpg", 214 | }); 215 | qrcode8.saveImage({ 216 | path: "q4.jpg", 217 | }); 218 | 219 | qrcode5.toSVGText().then((data) => { 220 | console.info("======QRCode SVG Data Text======"); 221 | console.info(data); 222 | console.info(""); 223 | }); 224 | 225 | qrcode8.saveSVG({ 226 | path: "qrcode.svg", 227 | }); 228 | 229 | var streamConfig = { 230 | // ====== Basic 231 | text: "https://github.com/ushelp/EasyQRCodeJS-NodeJS", 232 | colorLight: "transparent", 233 | width: 400, 234 | height: 400, 235 | quietZone: 10, 236 | quietZoneColor: "transparent", 237 | }; 238 | 239 | async function generate() { 240 | var streamQrcode = new QRCode(streamConfig); 241 | 242 | const out = fs.createWriteStream(`qrcode-stream.png`); 243 | // const stream = await streamQrcode.toStream(); 244 | // stream.pipe(out); 245 | // out.on('finish', () => console.log('Finsihed')); 246 | 247 | streamQrcode.toStream().then((res) => { 248 | res.pipe(out).on("finish", () => console.log("Stream Finsihed")); 249 | }); 250 | } 251 | 252 | generate(); 253 | -------------------------------------------------------------------------------- /demo/hamburger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/hamburger.png -------------------------------------------------------------------------------- /demo/logo-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/logo-transparent.png -------------------------------------------------------------------------------- /demo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/logo.png -------------------------------------------------------------------------------- /demo/q.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q.jpg -------------------------------------------------------------------------------- /demo/q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q.png -------------------------------------------------------------------------------- /demo/q2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q2.jpg -------------------------------------------------------------------------------- /demo/q2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q2.png -------------------------------------------------------------------------------- /demo/q3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q3.jpg -------------------------------------------------------------------------------- /demo/q3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q3.png -------------------------------------------------------------------------------- /demo/q4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q4.jpg -------------------------------------------------------------------------------- /demo/q4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/q4.png -------------------------------------------------------------------------------- /demo/qrcode-stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/demo/qrcode-stream.png -------------------------------------------------------------------------------- /demo/qrcode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/images/QR_Code_Structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/doc/images/QR_Code_Structure.png -------------------------------------------------------------------------------- /doc/images/QR_Code_Structure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | image/svg+xml 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 1. Version information 20 | 2. Format information 21 | 3. Data and error correction keys 22 | 4. Required patterns 23 | 4.1. Position 24 | 4.2. Alignment 25 | 4.3. Timing 26 | 5. Quiet zone 27 | -------------------------------------------------------------------------------- /doc/images/demo-premium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/doc/images/demo-premium.png -------------------------------------------------------------------------------- /doc/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/EasyQRCodeJS-NodeJS/07c8a71904bc35edbcbf89feef26b0bb9fd09b48/doc/images/demo.png -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for easyqrcodejs-nodejs.js 2 | // Project: [https://github.com/ushelp/EasyQRCodeJS-NodeJS] 3 | // Definitions by: Ray 4 | 5 | export = QRCode; 6 | 7 | declare class QRCode { 8 | constructor(vOption: any); 9 | 10 | saveImage(saveOptions: any): any; 11 | 12 | saveSVG(saveOptions: any): any; 13 | 14 | toDataURL(format?: any): any; 15 | 16 | toSVGText(format?: any): any; 17 | 18 | toStream(format?: any): any; 19 | 20 | static CorrectLevel: { 21 | H: number; 22 | L: number; 23 | M: number; 24 | Q: number; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EasyQRCodeJS-NodeJS 3 | * 4 | * NodeJS QRCode generator. Can save image or svg to file, get standard base64 image data url text or get SVG serialized text. Cross-browser QRCode generator for pure javascript. Support Dot style, Logo, Background image, Colorful, Title etc. settings. support binary mode.(Running without DOM on server side) 5 | * 6 | * Version 4.5.4 7 | * 8 | * @author [ inthinkcolor@gmail.com ] 9 | * 10 | * @see https://github.com/ushelp/EasyQRCodeJS-NodeJS 11 | * @see http://www.easyproject.cn/easyqrcodejs/tryit.html 12 | * @see https://github.com/ushelp/EasyQRCodeJS 13 | * 14 | * Copyright 2017 Ray, EasyProject 15 | * Released under the MIT license 16 | * 17 | * [Node.js] 18 | * 19 | */ 20 | var { createCanvas, loadImage, Image } = require("canvas"); 21 | 22 | var jsdom = require("jsdom"); 23 | var C2S = require("./canvas2svg"); 24 | var fs = require("fs"); 25 | var { optimize } = require("svgo"); 26 | 27 | const { JSDOM } = jsdom; 28 | const win = new JSDOM().window; 29 | 30 | function QR8bitByte(data, binary, utf8WithoutBOM) { 31 | this.mode = QRMode.MODE_8BIT_BYTE; 32 | this.data = data; 33 | this.parsedData = []; 34 | 35 | function toUTF8Array(str) { 36 | var utf8 = []; 37 | for (var i = 0; i < str.length; i++) { 38 | var charcode = str.charCodeAt(i); 39 | if (charcode < 0x80) utf8.push(charcode); 40 | else if (charcode < 0x800) { 41 | utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f)); 42 | } else if (charcode < 0xd800 || charcode >= 0xe000) { 43 | utf8.push( 44 | 0xe0 | (charcode >> 12), 45 | 0x80 | ((charcode >> 6) & 0x3f), 46 | 0x80 | (charcode & 0x3f) 47 | ); 48 | } else { 49 | i++; 50 | charcode = 51 | 0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); 52 | utf8.push( 53 | 0xf0 | (charcode >> 18), 54 | 0x80 | ((charcode >> 12) & 0x3f), 55 | 0x80 | ((charcode >> 6) & 0x3f), 56 | 0x80 | (charcode & 0x3f) 57 | ); 58 | } 59 | } 60 | return utf8; 61 | } 62 | 63 | if (binary) { 64 | for (var i = 0, l = this.data.length; i < l; i++) { 65 | var byteArray = []; 66 | var code = this.data.charCodeAt(i); 67 | byteArray[0] = code; 68 | 69 | this.parsedData.push(byteArray); 70 | } 71 | this.parsedData = Array.prototype.concat.apply([], this.parsedData); 72 | } else { 73 | this.parsedData = toUTF8Array(data); 74 | } 75 | 76 | this.parsedData = Array.prototype.concat.apply([], this.parsedData); 77 | if (!utf8WithoutBOM && this.parsedData.length != this.data.length) { 78 | this.parsedData.unshift(191); 79 | this.parsedData.unshift(187); 80 | this.parsedData.unshift(239); 81 | } 82 | } 83 | 84 | QR8bitByte.prototype = { 85 | getLength: function (buffer) { 86 | return this.parsedData.length; 87 | }, 88 | write: function (buffer) { 89 | for (var i = 0, l = this.parsedData.length; i < l; i++) { 90 | buffer.put(this.parsedData[i], 8); 91 | } 92 | }, 93 | }; 94 | 95 | function QRCodeModel(typeNumber, errorCorrectLevel) { 96 | this.typeNumber = typeNumber; 97 | this.errorCorrectLevel = errorCorrectLevel; 98 | this.modules = null; 99 | this.moduleCount = 0; 100 | this.dataCache = null; 101 | this.dataList = []; 102 | } 103 | 104 | QRCodeModel.prototype = { 105 | addData: function (data, binary, utf8WithoutBOM) { 106 | var newData = new QR8bitByte(data, binary, utf8WithoutBOM); 107 | this.dataList.push(newData); 108 | this.dataCache = null; 109 | }, 110 | isDark: function (row, col) { 111 | if ( 112 | row < 0 || 113 | this.moduleCount <= row || 114 | col < 0 || 115 | this.moduleCount <= col 116 | ) { 117 | throw new Error(row + "," + col); 118 | } 119 | return this.modules[row][col][0]; 120 | }, 121 | getEye: function (row, col) { 122 | if ( 123 | row < 0 || 124 | this.moduleCount <= row || 125 | col < 0 || 126 | this.moduleCount <= col 127 | ) { 128 | throw new Error(row + "," + col); 129 | } 130 | 131 | var block = this.modules[row][col]; // [isDark(ture/false), EyeOuterOrInner(O/I), Position(TL/TR/BL/A) ] 132 | 133 | if (block[1]) { 134 | var type = "P" + block[1] + "_" + block[2]; //PO_TL, PI_TL, PO_TR, PI_TR, PO_BL, PI_BL 135 | if (block[2] == "A") { 136 | type = "A" + block[1]; // AI, AO 137 | } 138 | 139 | return { 140 | isDarkBlock: block[0], 141 | type: type, 142 | }; 143 | } else { 144 | return null; 145 | } 146 | }, 147 | getModuleCount: function () { 148 | return this.moduleCount; 149 | }, 150 | make: function () { 151 | this.makeImpl(false, this.getBestMaskPattern()); 152 | }, 153 | makeImpl: function (test, maskPattern) { 154 | this.moduleCount = this.typeNumber * 4 + 17; 155 | this.modules = new Array(this.moduleCount); 156 | for (var row = 0; row < this.moduleCount; row++) { 157 | this.modules[row] = new Array(this.moduleCount); 158 | for (var col = 0; col < this.moduleCount; col++) { 159 | this.modules[row][col] = []; // [isDark(ture/false), EyeOuterOrInner(O/I), Position(TL/TR/BL) ] 160 | } 161 | } 162 | this.setupPositionProbePattern(0, 0, "TL"); // TopLeft, TL 163 | this.setupPositionProbePattern(this.moduleCount - 7, 0, "BL"); // BotoomLeft, BL 164 | this.setupPositionProbePattern(0, this.moduleCount - 7, "TR"); // TopRight, TR 165 | this.setupPositionAdjustPattern("A"); // Alignment, A 166 | this.setupTimingPattern(); 167 | this.setupTypeInfo(test, maskPattern); 168 | if (this.typeNumber >= 7) { 169 | this.setupTypeNumber(test); 170 | } 171 | if (this.dataCache == null) { 172 | this.dataCache = QRCodeModel.createData( 173 | this.typeNumber, 174 | this.errorCorrectLevel, 175 | this.dataList 176 | ); 177 | } 178 | this.mapData(this.dataCache, maskPattern); 179 | }, 180 | setupPositionProbePattern: function (row, col, posName) { 181 | for (var r = -1; r <= 7; r++) { 182 | if (row + r <= -1 || this.moduleCount <= row + r) continue; 183 | for (var c = -1; c <= 7; c++) { 184 | if (col + c <= -1 || this.moduleCount <= col + c) continue; 185 | if ( 186 | (0 <= r && r <= 6 && (c == 0 || c == 6)) || 187 | (0 <= c && c <= 6 && (r == 0 || r == 6)) || 188 | (2 <= r && r <= 4 && 2 <= c && c <= 4) 189 | ) { 190 | this.modules[row + r][col + c][0] = true; 191 | 192 | this.modules[row + r][col + c][2] = posName; // Position 193 | if (r == -0 || c == -0 || r == 6 || c == 6) { 194 | this.modules[row + r][col + c][1] = "O"; // Position Outer 195 | } else { 196 | this.modules[row + r][col + c][1] = "I"; // Position Inner 197 | } 198 | } else { 199 | this.modules[row + r][col + c][0] = false; 200 | } 201 | } 202 | } 203 | }, 204 | getBestMaskPattern: function () { 205 | var minLostPoint = 0; 206 | var pattern = 0; 207 | for (var i = 0; i < 8; i++) { 208 | this.makeImpl(true, i); 209 | var lostPoint = QRUtil.getLostPoint(this); 210 | if (i == 0 || minLostPoint > lostPoint) { 211 | minLostPoint = lostPoint; 212 | pattern = i; 213 | } 214 | } 215 | return pattern; 216 | }, 217 | createMovieClip: function (target_mc, instance_name, depth) { 218 | var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth); 219 | var cs = 1; 220 | this.make(); 221 | for (var row = 0; row < this.modules.length; row++) { 222 | var y = row * cs; 223 | for (var col = 0; col < this.modules[row].length; col++) { 224 | var x = col * cs; 225 | var dark = this.modules[row][col][0]; 226 | if (dark) { 227 | qr_mc.beginFill(0, 100); 228 | qr_mc.moveTo(x, y); 229 | qr_mc.lineTo(x + cs, y); 230 | qr_mc.lineTo(x + cs, y + cs); 231 | qr_mc.lineTo(x, y + cs); 232 | qr_mc.endFill(); 233 | } 234 | } 235 | } 236 | return qr_mc; 237 | }, 238 | setupTimingPattern: function () { 239 | for (var r = 8; r < this.moduleCount - 8; r++) { 240 | if (this.modules[r][6][0] != null) { 241 | continue; 242 | } 243 | this.modules[r][6][0] = r % 2 == 0; 244 | } 245 | for (var c = 8; c < this.moduleCount - 8; c++) { 246 | if (this.modules[6][c][0] != null) { 247 | continue; 248 | } 249 | this.modules[6][c][0] = c % 2 == 0; 250 | } 251 | }, 252 | setupPositionAdjustPattern: function (posName) { 253 | var pos = QRUtil.getPatternPosition(this.typeNumber); 254 | for (var i = 0; i < pos.length; i++) { 255 | for (var j = 0; j < pos.length; j++) { 256 | var row = pos[i]; 257 | var col = pos[j]; 258 | if (this.modules[row][col][0] != null) { 259 | continue; 260 | } 261 | for (var r = -2; r <= 2; r++) { 262 | for (var c = -2; c <= 2; c++) { 263 | if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) { 264 | this.modules[row + r][col + c][0] = true; 265 | this.modules[row + r][col + c][2] = posName; // Position 266 | if (r == -2 || c == -2 || r == 2 || c == 2) { 267 | this.modules[row + r][col + c][1] = "O"; // Position Outer 268 | } else { 269 | this.modules[row + r][col + c][1] = "I"; // Position Inner 270 | } 271 | } else { 272 | this.modules[row + r][col + c][0] = false; 273 | } 274 | } 275 | } 276 | } 277 | } 278 | }, 279 | setupTypeNumber: function (test) { 280 | var bits = QRUtil.getBCHTypeNumber(this.typeNumber); 281 | for (var i = 0; i < 18; i++) { 282 | var mod = !test && ((bits >> i) & 1) == 1; 283 | this.modules[Math.floor(i / 3)][(i % 3) + this.moduleCount - 8 - 3][0] = 284 | mod; 285 | } 286 | for (var i = 0; i < 18; i++) { 287 | var mod = !test && ((bits >> i) & 1) == 1; 288 | this.modules[(i % 3) + this.moduleCount - 8 - 3][Math.floor(i / 3)][0] = 289 | mod; 290 | } 291 | }, 292 | setupTypeInfo: function (test, maskPattern) { 293 | var data = (this.errorCorrectLevel << 3) | maskPattern; 294 | var bits = QRUtil.getBCHTypeInfo(data); 295 | for (var i = 0; i < 15; i++) { 296 | var mod = !test && ((bits >> i) & 1) == 1; 297 | if (i < 6) { 298 | this.modules[i][8][0] = mod; 299 | } else if (i < 8) { 300 | this.modules[i + 1][8][0] = mod; 301 | } else { 302 | this.modules[this.moduleCount - 15 + i][8][0] = mod; 303 | } 304 | } 305 | for (var i = 0; i < 15; i++) { 306 | var mod = !test && ((bits >> i) & 1) == 1; 307 | if (i < 8) { 308 | this.modules[8][this.moduleCount - i - 1][0] = mod; 309 | } else if (i < 9) { 310 | this.modules[8][15 - i - 1 + 1][0] = mod; 311 | } else { 312 | this.modules[8][15 - i - 1][0] = mod; 313 | } 314 | } 315 | this.modules[this.moduleCount - 8][8][0] = !test; 316 | }, 317 | mapData: function (data, maskPattern) { 318 | var inc = -1; 319 | var row = this.moduleCount - 1; 320 | var bitIndex = 7; 321 | var byteIndex = 0; 322 | for (var col = this.moduleCount - 1; col > 0; col -= 2) { 323 | if (col == 6) col--; 324 | while (true) { 325 | for (var c = 0; c < 2; c++) { 326 | if (this.modules[row][col - c][0] == null) { 327 | var dark = false; 328 | if (byteIndex < data.length) { 329 | dark = ((data[byteIndex] >>> bitIndex) & 1) == 1; 330 | } 331 | var mask = QRUtil.getMask(maskPattern, row, col - c); 332 | if (mask) { 333 | dark = !dark; 334 | } 335 | this.modules[row][col - c][0] = dark; 336 | bitIndex--; 337 | if (bitIndex == -1) { 338 | byteIndex++; 339 | bitIndex = 7; 340 | } 341 | } 342 | } 343 | row += inc; 344 | if (row < 0 || this.moduleCount <= row) { 345 | row -= inc; 346 | inc = -inc; 347 | break; 348 | } 349 | } 350 | } 351 | }, 352 | }; 353 | QRCodeModel.PAD0 = 0xec; 354 | QRCodeModel.PAD1 = 0x11; 355 | QRCodeModel.createData = function (typeNumber, errorCorrectLevel, dataList) { 356 | var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); 357 | var buffer = new QRBitBuffer(); 358 | for (var i = 0; i < dataList.length; i++) { 359 | var data = dataList[i]; 360 | buffer.put(data.mode, 4); 361 | buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber)); 362 | data.write(buffer); 363 | } 364 | var totalDataCount = 0; 365 | for (var i = 0; i < rsBlocks.length; i++) { 366 | totalDataCount += rsBlocks[i].dataCount; 367 | } 368 | if (buffer.getLengthInBits() > totalDataCount * 8) { 369 | throw new Error( 370 | "code length overflow. (" + 371 | buffer.getLengthInBits() + 372 | ">" + 373 | totalDataCount * 8 + 374 | ")" 375 | ); 376 | } 377 | if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { 378 | buffer.put(0, 4); 379 | } 380 | while (buffer.getLengthInBits() % 8 != 0) { 381 | buffer.putBit(false); 382 | } 383 | while (true) { 384 | if (buffer.getLengthInBits() >= totalDataCount * 8) { 385 | break; 386 | } 387 | buffer.put(QRCodeModel.PAD0, 8); 388 | if (buffer.getLengthInBits() >= totalDataCount * 8) { 389 | break; 390 | } 391 | buffer.put(QRCodeModel.PAD1, 8); 392 | } 393 | return QRCodeModel.createBytes(buffer, rsBlocks); 394 | }; 395 | QRCodeModel.createBytes = function (buffer, rsBlocks) { 396 | var offset = 0; 397 | var maxDcCount = 0; 398 | var maxEcCount = 0; 399 | var dcdata = new Array(rsBlocks.length); 400 | var ecdata = new Array(rsBlocks.length); 401 | for (var r = 0; r < rsBlocks.length; r++) { 402 | var dcCount = rsBlocks[r].dataCount; 403 | var ecCount = rsBlocks[r].totalCount - dcCount; 404 | maxDcCount = Math.max(maxDcCount, dcCount); 405 | maxEcCount = Math.max(maxEcCount, ecCount); 406 | dcdata[r] = new Array(dcCount); 407 | for (var i = 0; i < dcdata[r].length; i++) { 408 | dcdata[r][i] = 0xff & buffer.buffer[i + offset]; 409 | } 410 | offset += dcCount; 411 | var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); 412 | var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1); 413 | var modPoly = rawPoly.mod(rsPoly); 414 | ecdata[r] = new Array(rsPoly.getLength() - 1); 415 | for (var i = 0; i < ecdata[r].length; i++) { 416 | var modIndex = i + modPoly.getLength() - ecdata[r].length; 417 | ecdata[r][i] = modIndex >= 0 ? modPoly.get(modIndex) : 0; 418 | } 419 | } 420 | var totalCodeCount = 0; 421 | for (var i = 0; i < rsBlocks.length; i++) { 422 | totalCodeCount += rsBlocks[i].totalCount; 423 | } 424 | var data = new Array(totalCodeCount); 425 | var index = 0; 426 | for (var i = 0; i < maxDcCount; i++) { 427 | for (var r = 0; r < rsBlocks.length; r++) { 428 | if (i < dcdata[r].length) { 429 | data[index++] = dcdata[r][i]; 430 | } 431 | } 432 | } 433 | for (var i = 0; i < maxEcCount; i++) { 434 | for (var r = 0; r < rsBlocks.length; r++) { 435 | if (i < ecdata[r].length) { 436 | data[index++] = ecdata[r][i]; 437 | } 438 | } 439 | } 440 | return data; 441 | }; 442 | var QRMode = { 443 | MODE_NUMBER: 1 << 0, 444 | MODE_ALPHA_NUM: 1 << 1, 445 | MODE_8BIT_BYTE: 1 << 2, 446 | MODE_KANJI: 1 << 3, 447 | }; 448 | var QRErrorCorrectLevel = { 449 | L: 1, 450 | M: 0, 451 | Q: 3, 452 | H: 2, 453 | }; 454 | var QRMaskPattern = { 455 | PATTERN000: 0, 456 | PATTERN001: 1, 457 | PATTERN010: 2, 458 | PATTERN011: 3, 459 | PATTERN100: 4, 460 | PATTERN101: 5, 461 | PATTERN110: 6, 462 | PATTERN111: 7, 463 | }; 464 | var QRUtil = { 465 | PATTERN_POSITION_TABLE: [ 466 | [], 467 | [6, 18], 468 | [6, 22], 469 | [6, 26], 470 | [6, 30], 471 | [6, 34], 472 | [6, 22, 38], 473 | [6, 24, 42], 474 | [6, 26, 46], 475 | [6, 28, 50], 476 | [6, 30, 54], 477 | [6, 32, 58], 478 | [6, 34, 62], 479 | [6, 26, 46, 66], 480 | [6, 26, 48, 70], 481 | [6, 26, 50, 74], 482 | [6, 30, 54, 78], 483 | [6, 30, 56, 82], 484 | [6, 30, 58, 86], 485 | [6, 34, 62, 90], 486 | [6, 28, 50, 72, 94], 487 | [6, 26, 50, 74, 98], 488 | [6, 30, 54, 78, 102], 489 | [6, 28, 54, 80, 106], 490 | [6, 32, 58, 84, 110], 491 | [6, 30, 58, 86, 114], 492 | [6, 34, 62, 90, 118], 493 | [6, 26, 50, 74, 98, 122], 494 | [6, 30, 54, 78, 102, 126], 495 | [6, 26, 52, 78, 104, 130], 496 | [6, 30, 56, 82, 108, 134], 497 | [6, 34, 60, 86, 112, 138], 498 | [6, 30, 58, 86, 114, 142], 499 | [6, 34, 62, 90, 118, 146], 500 | [6, 30, 54, 78, 102, 126, 150], 501 | [6, 24, 50, 76, 102, 128, 154], 502 | [6, 28, 54, 80, 106, 132, 158], 503 | [6, 32, 58, 84, 110, 136, 162], 504 | [6, 26, 54, 82, 110, 138, 166], 505 | [6, 30, 58, 86, 114, 142, 170], 506 | ], 507 | G15: 508 | (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0), 509 | G18: 510 | (1 << 12) | 511 | (1 << 11) | 512 | (1 << 10) | 513 | (1 << 9) | 514 | (1 << 8) | 515 | (1 << 5) | 516 | (1 << 2) | 517 | (1 << 0), 518 | G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1), 519 | getBCHTypeInfo: function (data) { 520 | var d = data << 10; 521 | while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) { 522 | d ^= 523 | QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)); 524 | } 525 | return ((data << 10) | d) ^ QRUtil.G15_MASK; 526 | }, 527 | getBCHTypeNumber: function (data) { 528 | var d = data << 12; 529 | while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) { 530 | d ^= 531 | QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)); 532 | } 533 | return (data << 12) | d; 534 | }, 535 | getBCHDigit: function (data) { 536 | var digit = 0; 537 | while (data != 0) { 538 | digit++; 539 | data >>>= 1; 540 | } 541 | return digit; 542 | }, 543 | getPatternPosition: function (typeNumber) { 544 | return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]; 545 | }, 546 | getMask: function (maskPattern, i, j) { 547 | switch (maskPattern) { 548 | case QRMaskPattern.PATTERN000: 549 | return (i + j) % 2 == 0; 550 | case QRMaskPattern.PATTERN001: 551 | return i % 2 == 0; 552 | case QRMaskPattern.PATTERN010: 553 | return j % 3 == 0; 554 | case QRMaskPattern.PATTERN011: 555 | return (i + j) % 3 == 0; 556 | case QRMaskPattern.PATTERN100: 557 | return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0; 558 | case QRMaskPattern.PATTERN101: 559 | return ((i * j) % 2) + ((i * j) % 3) == 0; 560 | case QRMaskPattern.PATTERN110: 561 | return (((i * j) % 2) + ((i * j) % 3)) % 2 == 0; 562 | case QRMaskPattern.PATTERN111: 563 | return (((i * j) % 3) + ((i + j) % 2)) % 2 == 0; 564 | default: 565 | throw new Error("bad maskPattern:" + maskPattern); 566 | } 567 | }, 568 | getErrorCorrectPolynomial: function (errorCorrectLength) { 569 | var a = new QRPolynomial([1], 0); 570 | for (var i = 0; i < errorCorrectLength; i++) { 571 | a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0)); 572 | } 573 | return a; 574 | }, 575 | getLengthInBits: function (mode, type) { 576 | if (1 <= type && type < 10) { 577 | switch (mode) { 578 | case QRMode.MODE_NUMBER: 579 | return 10; 580 | case QRMode.MODE_ALPHA_NUM: 581 | return 9; 582 | case QRMode.MODE_8BIT_BYTE: 583 | return 8; 584 | case QRMode.MODE_KANJI: 585 | return 8; 586 | default: 587 | throw new Error("mode:" + mode); 588 | } 589 | } else if (type < 27) { 590 | switch (mode) { 591 | case QRMode.MODE_NUMBER: 592 | return 12; 593 | case QRMode.MODE_ALPHA_NUM: 594 | return 11; 595 | case QRMode.MODE_8BIT_BYTE: 596 | return 16; 597 | case QRMode.MODE_KANJI: 598 | return 10; 599 | default: 600 | throw new Error("mode:" + mode); 601 | } 602 | } else if (type < 41) { 603 | switch (mode) { 604 | case QRMode.MODE_NUMBER: 605 | return 14; 606 | case QRMode.MODE_ALPHA_NUM: 607 | return 13; 608 | case QRMode.MODE_8BIT_BYTE: 609 | return 16; 610 | case QRMode.MODE_KANJI: 611 | return 12; 612 | default: 613 | throw new Error("mode:" + mode); 614 | } 615 | } else { 616 | throw new Error("type:" + type); 617 | } 618 | }, 619 | getLostPoint: function (qrCode) { 620 | var moduleCount = qrCode.getModuleCount(); 621 | var lostPoint = 0; 622 | for (var row = 0; row < moduleCount; row++) { 623 | for (var col = 0; col < moduleCount; col++) { 624 | var sameCount = 0; 625 | var dark = qrCode.isDark(row, col); 626 | for (var r = -1; r <= 1; r++) { 627 | if (row + r < 0 || moduleCount <= row + r) { 628 | continue; 629 | } 630 | for (var c = -1; c <= 1; c++) { 631 | if (col + c < 0 || moduleCount <= col + c) { 632 | continue; 633 | } 634 | if (r == 0 && c == 0) { 635 | continue; 636 | } 637 | if (dark == qrCode.isDark(row + r, col + c)) { 638 | sameCount++; 639 | } 640 | } 641 | } 642 | if (sameCount > 5) { 643 | lostPoint += 3 + sameCount - 5; 644 | } 645 | } 646 | } 647 | for (var row = 0; row < moduleCount - 1; row++) { 648 | for (var col = 0; col < moduleCount - 1; col++) { 649 | var count = 0; 650 | if (qrCode.isDark(row, col)) count++; 651 | if (qrCode.isDark(row + 1, col)) count++; 652 | if (qrCode.isDark(row, col + 1)) count++; 653 | if (qrCode.isDark(row + 1, col + 1)) count++; 654 | if (count == 0 || count == 4) { 655 | lostPoint += 3; 656 | } 657 | } 658 | } 659 | for (var row = 0; row < moduleCount; row++) { 660 | for (var col = 0; col < moduleCount - 6; col++) { 661 | if ( 662 | qrCode.isDark(row, col) && 663 | !qrCode.isDark(row, col + 1) && 664 | qrCode.isDark(row, col + 2) && 665 | qrCode.isDark(row, col + 3) && 666 | qrCode.isDark(row, col + 4) && 667 | !qrCode.isDark(row, col + 5) && 668 | qrCode.isDark(row, col + 6) 669 | ) { 670 | lostPoint += 40; 671 | } 672 | } 673 | } 674 | for (var col = 0; col < moduleCount; col++) { 675 | for (var row = 0; row < moduleCount - 6; row++) { 676 | if ( 677 | qrCode.isDark(row, col) && 678 | !qrCode.isDark(row + 1, col) && 679 | qrCode.isDark(row + 2, col) && 680 | qrCode.isDark(row + 3, col) && 681 | qrCode.isDark(row + 4, col) && 682 | !qrCode.isDark(row + 5, col) && 683 | qrCode.isDark(row + 6, col) 684 | ) { 685 | lostPoint += 40; 686 | } 687 | } 688 | } 689 | var darkCount = 0; 690 | for (var col = 0; col < moduleCount; col++) { 691 | for (var row = 0; row < moduleCount; row++) { 692 | if (qrCode.isDark(row, col)) { 693 | darkCount++; 694 | } 695 | } 696 | } 697 | var ratio = 698 | Math.abs((100 * darkCount) / moduleCount / moduleCount - 50) / 5; 699 | lostPoint += ratio * 10; 700 | return lostPoint; 701 | }, 702 | }; 703 | var QRMath = { 704 | glog: function (n) { 705 | if (n < 1) { 706 | throw new Error("glog(" + n + ")"); 707 | } 708 | return QRMath.LOG_TABLE[n]; 709 | }, 710 | gexp: function (n) { 711 | while (n < 0) { 712 | n += 255; 713 | } 714 | while (n >= 256) { 715 | n -= 255; 716 | } 717 | return QRMath.EXP_TABLE[n]; 718 | }, 719 | EXP_TABLE: new Array(256), 720 | LOG_TABLE: new Array(256), 721 | }; 722 | for (var i = 0; i < 8; i++) { 723 | QRMath.EXP_TABLE[i] = 1 << i; 724 | } 725 | for (var i = 8; i < 256; i++) { 726 | QRMath.EXP_TABLE[i] = 727 | QRMath.EXP_TABLE[i - 4] ^ 728 | QRMath.EXP_TABLE[i - 5] ^ 729 | QRMath.EXP_TABLE[i - 6] ^ 730 | QRMath.EXP_TABLE[i - 8]; 731 | } 732 | for (var i = 0; i < 255; i++) { 733 | QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i; 734 | } 735 | 736 | function QRPolynomial(num, shift) { 737 | if (num.length == undefined) { 738 | throw new Error(num.length + "/" + shift); 739 | } 740 | var offset = 0; 741 | while (offset < num.length && num[offset] == 0) { 742 | offset++; 743 | } 744 | this.num = new Array(num.length - offset + shift); 745 | for (var i = 0; i < num.length - offset; i++) { 746 | this.num[i] = num[i + offset]; 747 | } 748 | } 749 | 750 | QRPolynomial.prototype = { 751 | get: function (index) { 752 | return this.num[index]; 753 | }, 754 | getLength: function () { 755 | return this.num.length; 756 | }, 757 | multiply: function (e) { 758 | var num = new Array(this.getLength() + e.getLength() - 1); 759 | for (var i = 0; i < this.getLength(); i++) { 760 | for (var j = 0; j < e.getLength(); j++) { 761 | num[i + j] ^= QRMath.gexp( 762 | QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)) 763 | ); 764 | } 765 | } 766 | return new QRPolynomial(num, 0); 767 | }, 768 | mod: function (e) { 769 | if (this.getLength() - e.getLength() < 0) { 770 | return this; 771 | } 772 | var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0)); 773 | var num = new Array(this.getLength()); 774 | for (var i = 0; i < this.getLength(); i++) { 775 | num[i] = this.get(i); 776 | } 777 | for (var i = 0; i < e.getLength(); i++) { 778 | num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio); 779 | } 780 | return new QRPolynomial(num, 0).mod(e); 781 | }, 782 | }; 783 | 784 | function QRRSBlock(totalCount, dataCount) { 785 | this.totalCount = totalCount; 786 | this.dataCount = dataCount; 787 | } 788 | 789 | QRRSBlock.RS_BLOCK_TABLE = [ 790 | [1, 26, 19], 791 | [1, 26, 16], 792 | [1, 26, 13], 793 | [1, 26, 9], 794 | [1, 44, 34], 795 | [1, 44, 28], 796 | [1, 44, 22], 797 | [1, 44, 16], 798 | [1, 70, 55], 799 | [1, 70, 44], 800 | [2, 35, 17], 801 | [2, 35, 13], 802 | [1, 100, 80], 803 | [2, 50, 32], 804 | [2, 50, 24], 805 | [4, 25, 9], 806 | [1, 134, 108], 807 | [2, 67, 43], 808 | [2, 33, 15, 2, 34, 16], 809 | [2, 33, 11, 2, 34, 12], 810 | [2, 86, 68], 811 | [4, 43, 27], 812 | [4, 43, 19], 813 | [4, 43, 15], 814 | [2, 98, 78], 815 | [4, 49, 31], 816 | [2, 32, 14, 4, 33, 15], 817 | [4, 39, 13, 1, 40, 14], 818 | [2, 121, 97], 819 | [2, 60, 38, 2, 61, 39], 820 | [4, 40, 18, 2, 41, 19], 821 | [4, 40, 14, 2, 41, 15], 822 | [2, 146, 116], 823 | [3, 58, 36, 2, 59, 37], 824 | [4, 36, 16, 4, 37, 17], 825 | [4, 36, 12, 4, 37, 13], 826 | [2, 86, 68, 2, 87, 69], 827 | [4, 69, 43, 1, 70, 44], 828 | [6, 43, 19, 2, 44, 20], 829 | [6, 43, 15, 2, 44, 16], 830 | [4, 101, 81], 831 | [1, 80, 50, 4, 81, 51], 832 | [4, 50, 22, 4, 51, 23], 833 | [3, 36, 12, 8, 37, 13], 834 | [2, 116, 92, 2, 117, 93], 835 | [6, 58, 36, 2, 59, 37], 836 | [4, 46, 20, 6, 47, 21], 837 | [7, 42, 14, 4, 43, 15], 838 | [4, 133, 107], 839 | [8, 59, 37, 1, 60, 38], 840 | [8, 44, 20, 4, 45, 21], 841 | [12, 33, 11, 4, 34, 12], 842 | [3, 145, 115, 1, 146, 116], 843 | [4, 64, 40, 5, 65, 41], 844 | [11, 36, 16, 5, 37, 17], 845 | [11, 36, 12, 5, 37, 13], 846 | [5, 109, 87, 1, 110, 88], 847 | [5, 65, 41, 5, 66, 42], 848 | [5, 54, 24, 7, 55, 25], 849 | [11, 36, 12, 7, 37, 13], 850 | [5, 122, 98, 1, 123, 99], 851 | [7, 73, 45, 3, 74, 46], 852 | [15, 43, 19, 2, 44, 20], 853 | [3, 45, 15, 13, 46, 16], 854 | [1, 135, 107, 5, 136, 108], 855 | [10, 74, 46, 1, 75, 47], 856 | [1, 50, 22, 15, 51, 23], 857 | [2, 42, 14, 17, 43, 15], 858 | [5, 150, 120, 1, 151, 121], 859 | [9, 69, 43, 4, 70, 44], 860 | [17, 50, 22, 1, 51, 23], 861 | [2, 42, 14, 19, 43, 15], 862 | [3, 141, 113, 4, 142, 114], 863 | [3, 70, 44, 11, 71, 45], 864 | [17, 47, 21, 4, 48, 22], 865 | [9, 39, 13, 16, 40, 14], 866 | [3, 135, 107, 5, 136, 108], 867 | [3, 67, 41, 13, 68, 42], 868 | [15, 54, 24, 5, 55, 25], 869 | [15, 43, 15, 10, 44, 16], 870 | [4, 144, 116, 4, 145, 117], 871 | [17, 68, 42], 872 | [17, 50, 22, 6, 51, 23], 873 | [19, 46, 16, 6, 47, 17], 874 | [2, 139, 111, 7, 140, 112], 875 | [17, 74, 46], 876 | [7, 54, 24, 16, 55, 25], 877 | [34, 37, 13], 878 | [4, 151, 121, 5, 152, 122], 879 | [4, 75, 47, 14, 76, 48], 880 | [11, 54, 24, 14, 55, 25], 881 | [16, 45, 15, 14, 46, 16], 882 | [6, 147, 117, 4, 148, 118], 883 | [6, 73, 45, 14, 74, 46], 884 | [11, 54, 24, 16, 55, 25], 885 | [30, 46, 16, 2, 47, 17], 886 | [8, 132, 106, 4, 133, 107], 887 | [8, 75, 47, 13, 76, 48], 888 | [7, 54, 24, 22, 55, 25], 889 | [22, 45, 15, 13, 46, 16], 890 | [10, 142, 114, 2, 143, 115], 891 | [19, 74, 46, 4, 75, 47], 892 | [28, 50, 22, 6, 51, 23], 893 | [33, 46, 16, 4, 47, 17], 894 | [8, 152, 122, 4, 153, 123], 895 | [22, 73, 45, 3, 74, 46], 896 | [8, 53, 23, 26, 54, 24], 897 | [12, 45, 15, 28, 46, 16], 898 | [3, 147, 117, 10, 148, 118], 899 | [3, 73, 45, 23, 74, 46], 900 | [4, 54, 24, 31, 55, 25], 901 | [11, 45, 15, 31, 46, 16], 902 | [7, 146, 116, 7, 147, 117], 903 | [21, 73, 45, 7, 74, 46], 904 | [1, 53, 23, 37, 54, 24], 905 | [19, 45, 15, 26, 46, 16], 906 | [5, 145, 115, 10, 146, 116], 907 | [19, 75, 47, 10, 76, 48], 908 | [15, 54, 24, 25, 55, 25], 909 | [23, 45, 15, 25, 46, 16], 910 | [13, 145, 115, 3, 146, 116], 911 | [2, 74, 46, 29, 75, 47], 912 | [42, 54, 24, 1, 55, 25], 913 | [23, 45, 15, 28, 46, 16], 914 | [17, 145, 115], 915 | [10, 74, 46, 23, 75, 47], 916 | [10, 54, 24, 35, 55, 25], 917 | [19, 45, 15, 35, 46, 16], 918 | [17, 145, 115, 1, 146, 116], 919 | [14, 74, 46, 21, 75, 47], 920 | [29, 54, 24, 19, 55, 25], 921 | [11, 45, 15, 46, 46, 16], 922 | [13, 145, 115, 6, 146, 116], 923 | [14, 74, 46, 23, 75, 47], 924 | [44, 54, 24, 7, 55, 25], 925 | [59, 46, 16, 1, 47, 17], 926 | [12, 151, 121, 7, 152, 122], 927 | [12, 75, 47, 26, 76, 48], 928 | [39, 54, 24, 14, 55, 25], 929 | [22, 45, 15, 41, 46, 16], 930 | [6, 151, 121, 14, 152, 122], 931 | [6, 75, 47, 34, 76, 48], 932 | [46, 54, 24, 10, 55, 25], 933 | [2, 45, 15, 64, 46, 16], 934 | [17, 152, 122, 4, 153, 123], 935 | [29, 74, 46, 14, 75, 47], 936 | [49, 54, 24, 10, 55, 25], 937 | [24, 45, 15, 46, 46, 16], 938 | [4, 152, 122, 18, 153, 123], 939 | [13, 74, 46, 32, 75, 47], 940 | [48, 54, 24, 14, 55, 25], 941 | [42, 45, 15, 32, 46, 16], 942 | [20, 147, 117, 4, 148, 118], 943 | [40, 75, 47, 7, 76, 48], 944 | [43, 54, 24, 22, 55, 25], 945 | [10, 45, 15, 67, 46, 16], 946 | [19, 148, 118, 6, 149, 119], 947 | [18, 75, 47, 31, 76, 48], 948 | [34, 54, 24, 34, 55, 25], 949 | [20, 45, 15, 61, 46, 16], 950 | ]; 951 | QRRSBlock.getRSBlocks = function (typeNumber, errorCorrectLevel) { 952 | var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); 953 | if (rsBlock == undefined) { 954 | throw new Error( 955 | "bad rs block @ typeNumber:" + 956 | typeNumber + 957 | "/errorCorrectLevel:" + 958 | errorCorrectLevel 959 | ); 960 | } 961 | var length = rsBlock.length / 3; 962 | var list = []; 963 | for (var i = 0; i < length; i++) { 964 | var count = rsBlock[i * 3 + 0]; 965 | var totalCount = rsBlock[i * 3 + 1]; 966 | var dataCount = rsBlock[i * 3 + 2]; 967 | for (var j = 0; j < count; j++) { 968 | list.push(new QRRSBlock(totalCount, dataCount)); 969 | } 970 | } 971 | return list; 972 | }; 973 | QRRSBlock.getRsBlockTable = function (typeNumber, errorCorrectLevel) { 974 | switch (errorCorrectLevel) { 975 | case QRErrorCorrectLevel.L: 976 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; 977 | case QRErrorCorrectLevel.M: 978 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; 979 | case QRErrorCorrectLevel.Q: 980 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; 981 | case QRErrorCorrectLevel.H: 982 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; 983 | default: 984 | return undefined; 985 | } 986 | }; 987 | 988 | function QRBitBuffer() { 989 | this.buffer = []; 990 | this.length = 0; 991 | } 992 | 993 | QRBitBuffer.prototype = { 994 | get: function (index) { 995 | var bufIndex = Math.floor(index / 8); 996 | return ((this.buffer[bufIndex] >>> (7 - (index % 8))) & 1) == 1; 997 | }, 998 | put: function (num, length) { 999 | for (var i = 0; i < length; i++) { 1000 | this.putBit(((num >>> (length - i - 1)) & 1) == 1); 1001 | } 1002 | }, 1003 | getLengthInBits: function () { 1004 | return this.length; 1005 | }, 1006 | putBit: function (bit) { 1007 | var bufIndex = Math.floor(this.length / 8); 1008 | if (this.buffer.length <= bufIndex) { 1009 | this.buffer.push(0); 1010 | } 1011 | if (bit) { 1012 | this.buffer[bufIndex] |= 0x80 >>> this.length % 8; 1013 | } 1014 | this.length++; 1015 | }, 1016 | }; 1017 | var QRCodeLimitLength = [ 1018 | [17, 14, 11, 7], 1019 | [32, 26, 20, 14], 1020 | [53, 42, 32, 24], 1021 | [78, 62, 46, 34], 1022 | [106, 84, 60, 44], 1023 | [134, 106, 74, 58], 1024 | [154, 122, 86, 64], 1025 | [192, 152, 108, 84], 1026 | [230, 180, 130, 98], 1027 | [271, 213, 151, 119], 1028 | [321, 251, 177, 137], 1029 | [367, 287, 203, 155], 1030 | [425, 331, 241, 177], 1031 | [458, 362, 258, 194], 1032 | [520, 412, 292, 220], 1033 | [586, 450, 322, 250], 1034 | [644, 504, 364, 280], 1035 | [718, 560, 394, 310], 1036 | [792, 624, 442, 338], 1037 | [858, 666, 482, 382], 1038 | [929, 711, 509, 403], 1039 | [1003, 779, 565, 439], 1040 | [1091, 857, 611, 461], 1041 | [1171, 911, 661, 511], 1042 | [1273, 997, 715, 535], 1043 | [1367, 1059, 751, 593], 1044 | [1465, 1125, 805, 625], 1045 | [1528, 1190, 868, 658], 1046 | [1628, 1264, 908, 698], 1047 | [1732, 1370, 982, 742], 1048 | [1840, 1452, 1030, 790], 1049 | [1952, 1538, 1112, 842], 1050 | [2068, 1628, 1168, 898], 1051 | [2188, 1722, 1228, 958], 1052 | [2303, 1809, 1283, 983], 1053 | [2431, 1911, 1351, 1051], 1054 | [2563, 1989, 1423, 1093], 1055 | [2699, 2099, 1499, 1139], 1056 | [2809, 2213, 1579, 1219], 1057 | [2953, 2331, 1663, 1273], 1058 | ]; 1059 | 1060 | /** 1061 | * Get the type by string length 1062 | * 1063 | * @private 1064 | * @param {String} sText 1065 | * @param {Number} nCorrectLevel 1066 | * @return {Number} type 1067 | */ 1068 | function _getTypeNumber(sText, _htOption) { 1069 | var nCorrectLevel = _htOption.correctLevel; 1070 | 1071 | var nType = 1; 1072 | var length = _getUTF8Length(sText); 1073 | 1074 | for (var i = 0, len = QRCodeLimitLength.length; i < len; i++) { 1075 | var nLimit = 0; 1076 | 1077 | switch (nCorrectLevel) { 1078 | case QRErrorCorrectLevel.L: 1079 | nLimit = QRCodeLimitLength[i][0]; 1080 | break; 1081 | case QRErrorCorrectLevel.M: 1082 | nLimit = QRCodeLimitLength[i][1]; 1083 | break; 1084 | case QRErrorCorrectLevel.Q: 1085 | nLimit = QRCodeLimitLength[i][2]; 1086 | break; 1087 | case QRErrorCorrectLevel.H: 1088 | nLimit = QRCodeLimitLength[i][3]; 1089 | break; 1090 | } 1091 | 1092 | if (length <= nLimit) { 1093 | break; 1094 | } else { 1095 | nType++; 1096 | } 1097 | } 1098 | if (nType > QRCodeLimitLength.length) { 1099 | throw new Error( 1100 | "Too long data. the CorrectLevel." + 1101 | ["M", "L", "H", "Q"][nCorrectLevel] + 1102 | " limit length is " + 1103 | nLimit 1104 | ); 1105 | } 1106 | 1107 | if (_htOption.version != 0) { 1108 | if (nType <= _htOption.version) { 1109 | nType = _htOption.version; 1110 | _htOption.runVersion = nType; 1111 | } else { 1112 | console.warn( 1113 | "QR Code version " + 1114 | _htOption.version + 1115 | " too small, run version use " + 1116 | nType 1117 | ); 1118 | _htOption.runVersion = nType; 1119 | } 1120 | } 1121 | 1122 | return nType; 1123 | } 1124 | 1125 | function _getUTF8Length(sText) { 1126 | var replacedText = encodeURI(sText) 1127 | .toString() 1128 | .replace(/\%[0-9a-fA-F]{2}/g, "a"); 1129 | return replacedText.length + (replacedText.length != sText.length ? 3 : 0); 1130 | } 1131 | 1132 | /** 1133 | * Drawing QRCode by using canvas 1134 | * 1135 | * @constructor 1136 | * @param {HTMLElement} el 1137 | * @param {Object} htOption QRCode Options 1138 | */ 1139 | var Drawing = function (htOption) { 1140 | this._bIsPainted = false; 1141 | this._htOption = htOption; 1142 | this._canvas = createCanvas(200, 200); 1143 | if (this._htOption._drawer == "svg") { 1144 | this._oContext = {}; 1145 | } else { 1146 | this._oContext = this._canvas.getContext("2d"); 1147 | } 1148 | 1149 | this._bSupportDataURI = null; 1150 | }; 1151 | 1152 | /** 1153 | * Draw the QRCode 1154 | * 1155 | * @param {QRCode} oQRCode 1156 | */ 1157 | Drawing.prototype.draw = function (oQRCode) { 1158 | var _htOption = this._htOption; 1159 | 1160 | // QRCode Size 1161 | var nCount = oQRCode.getModuleCount(); 1162 | var nWidth = _htOption.width / nCount; 1163 | var nHeight = _htOption.height / nCount; 1164 | 1165 | if (nWidth <= 1) { 1166 | nWidth = 1; 1167 | } 1168 | if (nHeight <= 1) { 1169 | nHeight = 1; 1170 | } 1171 | 1172 | nWidth = Math.round(nWidth); 1173 | nHeight = Math.round(nHeight); 1174 | 1175 | var calculatedQRWidth = nWidth * nCount; 1176 | var calculatedQRHeight = nHeight * nCount; 1177 | 1178 | _htOption.heightWithTitle = calculatedQRHeight + _htOption.titleHeight; 1179 | _htOption.realHeight = _htOption.heightWithTitle + _htOption.quietZone * 2; 1180 | _htOption.realWidth = calculatedQRWidth + _htOption.quietZone * 2; 1181 | _htOption.calculatedQRWidth = calculatedQRWidth; 1182 | _htOption.calculatedQRHeight = calculatedQRHeight; 1183 | 1184 | this._canvas.width = _htOption.realWidth; 1185 | this._canvas.height = _htOption.realHeight; 1186 | 1187 | if (_htOption._drawer == "svg") { 1188 | this._oContext = new C2S({ 1189 | document: win.document, 1190 | XMLSerializer: win.XMLSerializer, 1191 | width: _htOption.width + this._htOption.quietZone * 2, 1192 | height: 1193 | _htOption.height + _htOption.titleHeight + this._htOption.quietZone * 2, 1194 | veiwBoxWidth: _htOption.realWidth, 1195 | veiwBoxHeight: _htOption.realHeight, 1196 | }); 1197 | } 1198 | 1199 | this._oContext.patternQuality = "best"; //'fast'|'good'|'best'|'nearest'|'bilinear' 1200 | this._oContext.quality = "best"; //'fast'|'good'|'best'|'nearest'|'bilinear' 1201 | this._oContext.textDrawingMode = "path"; // 'path'|'glyph' 1202 | this._oContext.antialias = "gray"; // 'default'|'none'|'gray'|'subpixel' 1203 | 1204 | var _oContext = this._oContext; 1205 | 1206 | var autoColorDark = _htOption.autoColorDark; 1207 | var autoColorLight = _htOption.autoColorLight; 1208 | var notAutoColorLight = "rgba(0,0,0,0)"; 1209 | 1210 | // JPG 1211 | if (_htOption.format == "JPG") { 1212 | _htOption.logoBackgroundTransparent = false; 1213 | 1214 | autoColorDark = _htOption.colorDark; 1215 | autoColorLight = _htOption.colorLight; 1216 | notAutoColorLight = _htOption.colorLight; 1217 | 1218 | if (_htOption.backgroundImage) { 1219 | _oContext.fillStyle = _htOption.colorLight; 1220 | _oContext.fillRect(0, 0, this._canvas.width, this._canvas.height); 1221 | } else { 1222 | if ( 1223 | _htOption.quietZoneColor == "rgba(0,0,0,0)" || 1224 | _htOption.quietZoneColor == "transparent" 1225 | ) { 1226 | _htOption.quietZoneColor = "#ffffff"; 1227 | } 1228 | _oContext.fillStyle = _htOption.colorLight; 1229 | _oContext.fillRect(0, 0, this._canvas.width, this._canvas.height); 1230 | } 1231 | } else { 1232 | _oContext.lineWidth = 0; 1233 | _oContext.fillStyle = _htOption.colorLight; 1234 | _oContext.fillRect(0, 0, this._canvas.width, this._canvas.height); 1235 | _oContext.clearRect( 1236 | _htOption.quietZone, 1237 | _htOption.quietZone, 1238 | _htOption.calculatedQRWidth, 1239 | _htOption.titleHeight 1240 | ); 1241 | } 1242 | 1243 | var t = this; 1244 | 1245 | function drawQuietZoneColor() { 1246 | if (_htOption.quietZone > 0 && _htOption.quietZoneColor) { 1247 | // top 1248 | _oContext.lineWidth = 0; 1249 | _oContext.fillStyle = _htOption.quietZoneColor; 1250 | 1251 | _oContext.fillRect(0, 0, t._canvas.width, _htOption.quietZone); 1252 | // left 1253 | _oContext.fillRect( 1254 | 0, 1255 | _htOption.quietZone, 1256 | _htOption.quietZone, 1257 | t._canvas.height - _htOption.quietZone * 2 1258 | ); 1259 | // right 1260 | _oContext.fillRect( 1261 | t._canvas.width - _htOption.quietZone, 1262 | _htOption.quietZone, 1263 | _htOption.quietZone, 1264 | t._canvas.height - _htOption.quietZone * 2 1265 | ); 1266 | // bottom 1267 | _oContext.fillRect( 1268 | 0, 1269 | t._canvas.height - _htOption.quietZone, 1270 | t._canvas.width, 1271 | _htOption.quietZone 1272 | ); 1273 | } 1274 | } 1275 | 1276 | if (_htOption.backgroundImage) { 1277 | // backgroundImage 1278 | var bgImg = new Image(); 1279 | bgImg.onload = function () { 1280 | _oContext.globalAlpha = 1; 1281 | _oContext.globalAlpha = _htOption.backgroundImageAlpha; 1282 | if ((_htOption.title || _htOption.subTitle) && _htOption.titleHeight) { 1283 | _oContext.drawImage( 1284 | bgImg, 1285 | _htOption.quietZone, 1286 | _htOption.quietZone + _htOption.titleHeight, 1287 | _htOption.width, 1288 | _htOption.height 1289 | ); 1290 | } else { 1291 | _oContext.drawImage( 1292 | bgImg, 1293 | 0, 1294 | 0, 1295 | _htOption.realWidth, 1296 | _htOption.realHeight 1297 | ); 1298 | } 1299 | 1300 | _oContext.globalAlpha = 1; 1301 | 1302 | drawQrcode.call(t, oQRCode); 1303 | }; 1304 | bgImg.onerror = function (e) { 1305 | t.reject(e); 1306 | }; 1307 | bgImg.originalSrc = _htOption.backgroundImage; 1308 | bgImg.src = _htOption.backgroundImage; 1309 | // DoSomething 1310 | } else { 1311 | drawQrcode.call(t, oQRCode); 1312 | } 1313 | 1314 | function drawQrcode(oQRCode) { 1315 | for (var row = 0; row < nCount; row++) { 1316 | for (var col = 0; col < nCount; col++) { 1317 | var nLeft = col * nWidth + _htOption.quietZone; 1318 | var nTop = row * nHeight + _htOption.quietZone; 1319 | 1320 | var bIsDark = oQRCode.isDark(row, col); 1321 | 1322 | var eye = oQRCode.getEye(row, col); // { isDark: true/false, type: PO_TL, PI_TL, PO_TR, PI_TR, PO_BL, PI_BL }; 1323 | 1324 | var nowDotScale = _htOption.dotScale; 1325 | 1326 | _oContext.lineWidth = 0; 1327 | // Color handler 1328 | var dColor; 1329 | var lColor; 1330 | if (eye) { 1331 | dColor = 1332 | _htOption[eye.type] || 1333 | _htOption[eye.type.substring(0, 2)] || 1334 | _htOption.colorDark; 1335 | lColor = _htOption.colorLight; 1336 | } else { 1337 | if (_htOption.backgroundImage) { 1338 | lColor = "rgba(0,0,0,0)"; 1339 | if (row == 6) { 1340 | // dColor = _htOption.timing_H || _htOption.timing || _htOption.colorDark; 1341 | if (_htOption.autoColor) { 1342 | dColor = 1343 | _htOption.timing_H || 1344 | _htOption.timing || 1345 | _htOption.autoColorDark; 1346 | lColor = _htOption.autoColorLight; 1347 | } else { 1348 | dColor = 1349 | _htOption.timing_H || _htOption.timing || _htOption.colorDark; 1350 | } 1351 | } else if (col == 6) { 1352 | // dColor = _htOption.timing_V || _htOption.timing || _htOption.colorDark; 1353 | if (_htOption.autoColor) { 1354 | dColor = 1355 | _htOption.timing_V || 1356 | _htOption.timing || 1357 | _htOption.autoColorDark; 1358 | lColor = _htOption.autoColorLight; 1359 | } else { 1360 | dColor = 1361 | _htOption.timing_V || _htOption.timing || _htOption.colorDark; 1362 | } 1363 | } else { 1364 | if (_htOption.autoColor) { 1365 | dColor = _htOption.autoColorDark; 1366 | lColor = _htOption.autoColorLight; 1367 | } else { 1368 | dColor = _htOption.colorDark; 1369 | } 1370 | } 1371 | } else { 1372 | if (row == 6) { 1373 | dColor = 1374 | _htOption.timing_H || _htOption.timing || _htOption.colorDark; 1375 | } else if (col == 6) { 1376 | dColor = 1377 | _htOption.timing_V || _htOption.timing || _htOption.colorDark; 1378 | } else { 1379 | dColor = _htOption.colorDark; 1380 | } 1381 | lColor = _htOption.colorLight; 1382 | } 1383 | } 1384 | _oContext.strokeStyle = bIsDark ? dColor : lColor; 1385 | _oContext.fillStyle = bIsDark ? dColor : lColor; 1386 | 1387 | if (eye) { 1388 | // Is eye 1389 | bIsDark = eye.isDarkBlock; 1390 | var type = eye.type; 1391 | if (type == "AO") { 1392 | nowDotScale = _htOption.dotScaleAO; 1393 | } else if (type == "AI") { 1394 | nowDotScale = _htOption.dotScaleAI; 1395 | } else { 1396 | nowDotScale = 1; 1397 | } 1398 | 1399 | if (_htOption.backgroundImage && _htOption.autoColor) { 1400 | dColor = 1401 | (eye.type == "AO" ? _htOption.AI : _htOption.AO) || 1402 | _htOption.autoColorDark; 1403 | lColor = _htOption.autoColorLight; 1404 | } else { 1405 | dColor = (eye.type == "AO" ? _htOption.AI : _htOption.AO) || dColor; 1406 | } 1407 | 1408 | _oContext.fillRect( 1409 | Math.ceil(nLeft + (nWidth * (1 - nowDotScale)) / 2), 1410 | Math.ceil( 1411 | _htOption.titleHeight + nTop + (nHeight * (1 - nowDotScale)) / 2 1412 | ), 1413 | Math.ceil(nWidth * nowDotScale), 1414 | Math.ceil(nHeight * nowDotScale) 1415 | ); 1416 | } else { 1417 | if (row == 6) { 1418 | // Timing Pattern 1419 | nowDotScale = _htOption.dotScaleTiming_H; 1420 | 1421 | _oContext.fillRect( 1422 | Math.ceil(nLeft + (nWidth * (1 - nowDotScale)) / 2), 1423 | Math.ceil( 1424 | _htOption.titleHeight + nTop + (nHeight * (1 - nowDotScale)) / 2 1425 | ), 1426 | Math.ceil(nWidth * nowDotScale), 1427 | Math.ceil(nHeight * nowDotScale) 1428 | ); 1429 | } else if (col == 6) { 1430 | // Timing Pattern 1431 | nowDotScale = _htOption.dotScaleTiming_V; 1432 | 1433 | _oContext.fillRect( 1434 | Math.ceil(nLeft + (nWidth * (1 - nowDotScale)) / 2), 1435 | Math.ceil( 1436 | _htOption.titleHeight + nTop + (nHeight * (1 - nowDotScale)) / 2 1437 | ), 1438 | Math.ceil(nWidth * nowDotScale), 1439 | Math.ceil(nHeight * nowDotScale) 1440 | ); 1441 | } else { 1442 | if (_htOption.backgroundImage) { 1443 | _oContext.fillRect( 1444 | Math.ceil(nLeft + (nWidth * (1 - nowDotScale)) / 2), 1445 | Math.ceil( 1446 | _htOption.titleHeight + 1447 | nTop + 1448 | (nHeight * (1 - nowDotScale)) / 2 1449 | ), 1450 | Math.ceil(nWidth * nowDotScale), 1451 | Math.ceil(nHeight * nowDotScale) 1452 | ); 1453 | } else { 1454 | _oContext.fillRect( 1455 | Math.ceil(nLeft + (nWidth * (1 - nowDotScale)) / 2), 1456 | Math.ceil( 1457 | _htOption.titleHeight + 1458 | nTop + 1459 | (nHeight * (1 - nowDotScale)) / 2 1460 | ), 1461 | Math.ceil(nWidth * nowDotScale), 1462 | Math.ceil(nHeight * nowDotScale) 1463 | ); 1464 | } 1465 | } 1466 | } 1467 | 1468 | if (_htOption.dotScale != 1 && !eye) { 1469 | _oContext.strokeStyle = _htOption.colorLight; 1470 | } 1471 | } 1472 | } 1473 | 1474 | if (_htOption.title) { 1475 | _oContext.fillStyle = _htOption.titleBackgroundColor; 1476 | _oContext.fillRect( 1477 | _htOption.quietZone, 1478 | _htOption.quietZone, 1479 | _htOption.calculatedQRWidth, 1480 | _htOption.titleHeight 1481 | ); 1482 | 1483 | _oContext.font = _htOption.titleFont; 1484 | _oContext.fillStyle = _htOption.titleColor; 1485 | _oContext.textAlign = "center"; 1486 | _oContext.fillText( 1487 | _htOption.title, 1488 | t._canvas.width / 2, 1489 | _htOption.quietZone + _htOption.titleTop 1490 | ); 1491 | } 1492 | 1493 | if (_htOption.subTitle) { 1494 | _oContext.font = _htOption.subTitleFont; 1495 | _oContext.fillStyle = _htOption.subTitleColor; 1496 | _oContext.fillText( 1497 | _htOption.subTitle, 1498 | t._canvas.width / 2, 1499 | _htOption.quietZone + _htOption.subTitleTop 1500 | ); 1501 | } 1502 | 1503 | if (_htOption.logo) { 1504 | var img = new Image(); 1505 | 1506 | var _this = this; 1507 | 1508 | function generateLogoImg(img) { 1509 | var imgContainerW = Math.round(_htOption.width / 3.5); 1510 | var imgContainerH = Math.round(_htOption.height / 3.5); 1511 | if (imgContainerW !== imgContainerH) { 1512 | imgContainerW = imgContainerH; 1513 | } 1514 | 1515 | if (_htOption.logoMaxWidth) { 1516 | imgContainerW = Math.round(_htOption.logoMaxWidth); 1517 | } else if (_htOption.logoWidth) { 1518 | imgContainerW = Math.round(_htOption.logoWidth); 1519 | } 1520 | 1521 | if (_htOption.logoMaxHeight) { 1522 | imgContainerH = Math.round(_htOption.logoMaxHeight); 1523 | } else if (_htOption.logoHeight) { 1524 | imgContainerH = Math.round(_htOption.logoHeight); 1525 | } 1526 | 1527 | var nw; 1528 | var nh; 1529 | if (typeof img.naturalWidth == "undefined") { 1530 | // IE 6/7/8 1531 | nw = img.width; 1532 | nh = img.height; 1533 | } else { 1534 | // HTML5 browsers 1535 | nw = img.naturalWidth; 1536 | nh = img.naturalHeight; 1537 | } 1538 | 1539 | if (_htOption.logoMaxWidth || _htOption.logoMaxHeight) { 1540 | if (_htOption.logoMaxWidth && nw <= imgContainerW) { 1541 | imgContainerW = nw; 1542 | } 1543 | 1544 | if (_htOption.logoMaxHeight && nh <= imgContainerH) { 1545 | imgContainerH = nh; 1546 | } 1547 | if (nw <= imgContainerW && nh <= imgContainerH) { 1548 | imgContainerW = nw; 1549 | imgContainerH = nh; 1550 | } 1551 | } 1552 | 1553 | var imgContainerX = (_htOption.realWidth - imgContainerW) / 2; 1554 | var imgContainerY = 1555 | (_htOption.calculatedQRHeight - imgContainerH) / 2 + 1556 | _htOption.titleHeight + 1557 | _htOption.quietZone; 1558 | 1559 | var imgScale = Math.min(imgContainerW / nw, imgContainerH / nh); 1560 | var imgW = nw * imgScale; 1561 | var imgH = nh * imgScale; 1562 | 1563 | if (_htOption.logoMaxWidth || _htOption.logoMaxHeight) { 1564 | imgContainerW = imgW; 1565 | imgContainerH = imgH; 1566 | imgContainerX = (_htOption.realWidth - imgContainerW) / 2; 1567 | imgContainerY = (_htOption.realWidth - imgContainerH) / 2; 1568 | } 1569 | 1570 | // Did Not Use Transparent Logo Image 1571 | if (!_htOption.logoBackgroundTransparent) { 1572 | //if (!_htOption.logoBackgroundColor) { 1573 | //_htOption.logoBackgroundColor = '#ffffff'; 1574 | //} 1575 | _oContext.fillStyle = _htOption.logoBackgroundColor; 1576 | 1577 | _oContext.fillRect( 1578 | imgContainerX, 1579 | imgContainerY, 1580 | imgContainerW, 1581 | imgContainerH 1582 | ); 1583 | } 1584 | _oContext.drawImage( 1585 | img, 1586 | imgContainerX + (imgContainerW - imgW) / 2, 1587 | imgContainerY + (imgContainerH - imgH) / 2, 1588 | imgW, 1589 | imgH 1590 | ); 1591 | 1592 | drawQuietZoneColor(); 1593 | _this._bIsPainted = true; 1594 | _this.makeImage(); 1595 | } 1596 | 1597 | img.onload = function () { 1598 | generateLogoImg(img); 1599 | }; 1600 | img.onerror = function (e) { 1601 | // console.error(e); 1602 | t.reject(e); 1603 | }; 1604 | img.originalSrc = _htOption.logo; 1605 | img.src = _htOption.logo; 1606 | // if (img.complete) { 1607 | // img.onload = null; 1608 | // generateLogoImg(img); 1609 | // return; 1610 | // } 1611 | } else { 1612 | drawQuietZoneColor(); 1613 | this._bIsPainted = true; 1614 | this.makeImage(); 1615 | } 1616 | } 1617 | }; 1618 | 1619 | /** 1620 | * Make the image from Canvas 1621 | */ 1622 | Drawing.prototype.makeImage = function () { 1623 | var makeOptions = this.makeOptions; 1624 | var t = this; 1625 | 1626 | if (makeOptions.makeType == "FILE") { 1627 | if (this._htOption.onRenderingStart) { 1628 | this._htOption.onRenderingStart(this._htOption); 1629 | } 1630 | if (this._htOption._drawer == "svg") { 1631 | let data = this._oContext.getSerializedSvg(); 1632 | fs.writeFile( 1633 | makeOptions.path, 1634 | optimize(data).data, 1635 | "utf8", 1636 | function (err) { 1637 | if (err) { 1638 | t.reject(err); 1639 | } 1640 | t.resolve({}); 1641 | } 1642 | ); 1643 | } else { 1644 | function scaleCanvas(canvas, newWidth, newHeight) { 1645 | // var buffer = canvas.toBuffer('image/png'); 1646 | // canvas.width = newWidth; 1647 | // canvas.height = newHeight; 1648 | 1649 | // var img = new Image(); 1650 | // img.src = buffer; 1651 | 1652 | // img.onload = () => { 1653 | // canvas.width = newWidth; 1654 | // canvas.height = newHeight; 1655 | // ctx.drawImage(img, 0, 0, newWidth, newHeight); 1656 | // }; 1657 | 1658 | const newCanvas = createCanvas(newWidth, newHeight); 1659 | const newCtx = newCanvas.getContext("2d"); 1660 | 1661 | newCtx.drawImage(canvas, 0, 0, newWidth, newHeight); 1662 | 1663 | return newCanvas; 1664 | } 1665 | this._canvas = scaleCanvas( 1666 | this._canvas, 1667 | this._htOption.width + this._htOption.quietZone * 2, 1668 | this._htOption.height + 1669 | this._htOption.titleHeight + 1670 | this._htOption.quietZone * 2 1671 | ); 1672 | this._oContext = this._canvas.getContext("2d"); 1673 | var out = fs.createWriteStream(makeOptions.path); 1674 | 1675 | var stream = undefined; 1676 | 1677 | if (this._htOption.format == "PNG") { 1678 | stream = this._canvas.createPNGStream({ 1679 | compressionLevel: this._htOption.compressionLevel, 1680 | }); 1681 | } else { 1682 | stream = this._canvas.createJPEGStream({ 1683 | quality: this._htOption.quality, 1684 | }); 1685 | } 1686 | 1687 | stream.pipe(out); 1688 | out.on("finish", () => { 1689 | t.resolve({}); 1690 | }); 1691 | } 1692 | } else if (makeOptions.makeType == "URL") { 1693 | if (this._htOption.onRenderingStart) { 1694 | this._htOption.onRenderingStart(this._htOption); 1695 | } 1696 | if (this._htOption._drawer == "svg") { 1697 | let data = this._oContext.getSerializedSvg(); 1698 | t.resolve(optimize(data).data); 1699 | } else { 1700 | if (this._htOption.format == "PNG") { 1701 | // dataUrl = this._canvas.toDataURL() 1702 | this._canvas.toDataURL((err, data) => { 1703 | t.resolve(data); 1704 | }); // defaults to PNG 1705 | } else { 1706 | this._canvas.toDataURL("image/jpeg", (err, data) => { 1707 | t.resolve(data); 1708 | }); 1709 | } 1710 | } 1711 | } else if (makeOptions.makeType == "STREAM") { 1712 | if (this._htOption.onRenderingStart) { 1713 | this._htOption.onRenderingStart(this._htOption); 1714 | } 1715 | 1716 | if (this._htOption.format == "PNG") { 1717 | // dataUrl = this._canvas.toDataURL() 1718 | t.resolve(this._canvas.createPNGStream()); 1719 | } else { 1720 | t.resolve(this._canvas.createJPEGStream()); 1721 | } 1722 | } 1723 | }; 1724 | 1725 | /** 1726 | * Return whether the QRCode is painted or not 1727 | * 1728 | * @return {Boolean} 1729 | */ 1730 | Drawing.prototype.isPainted = function () { 1731 | return this._bIsPainted; 1732 | }; 1733 | 1734 | /** 1735 | * @private 1736 | * @param {Number} nNumber 1737 | */ 1738 | Drawing.prototype.round = function (nNumber) { 1739 | if (!nNumber) { 1740 | return nNumber; 1741 | } 1742 | return Math.floor(nNumber * 1000) / 1000; 1743 | }; 1744 | 1745 | function QRCode(vOption) { 1746 | this._htOption = { 1747 | width: 256, 1748 | height: 256, 1749 | typeNumber: 4, 1750 | colorDark: "#000000", 1751 | colorLight: "#ffffff", 1752 | correctLevel: QRErrorCorrectLevel.H, 1753 | 1754 | dotScale: 1, // For body block, must be greater than 0, less than or equal to 1. default is 1 1755 | 1756 | dotScaleTiming: 1, // Dafault for timing block , must be greater than 0, less than or equal to 1. default is 1 1757 | dotScaleTiming_H: undefined, // For horizontal timing block, must be greater than 0, less than or equal to 1. default is 1 1758 | dotScaleTiming_V: undefined, // For vertical timing block, must be greater than 0, less than or equal to 1. default is 1 1759 | 1760 | dotScaleA: 1, // Dafault for alignment block, must be greater than 0, less than or equal to 1. default is 1 1761 | dotScaleAO: undefined, // For alignment outer block, must be greater than 0, less than or equal to 1. default is 1 1762 | dotScaleAI: undefined, // For alignment inner block, must be greater than 0, less than or equal to 1. default is 1 1763 | 1764 | quietZone: 0, 1765 | quietZoneColor: "rgba(0,0,0,0)", 1766 | 1767 | title: "", 1768 | titleFont: "normal normal bold 16px Arial", 1769 | titleColor: "#000000", 1770 | titleBackgroundColor: "#ffffff", 1771 | titleHeight: 0, // Title Height, Include subTitle 1772 | titleTop: 30, // draws y coordinates. default is 30 1773 | 1774 | subTitle: "", 1775 | subTitleFont: "normal normal normal 14px Arial", 1776 | subTitleColor: "#4F4F4F", 1777 | subTitleTop: 60, // draws y coordinates. default is 0 1778 | 1779 | logo: undefined, 1780 | logoWidth: undefined, 1781 | logoHeight: undefined, 1782 | logoMaxWidth: undefined, 1783 | logoMaxHeight: undefined, 1784 | logoBackgroundColor: "#ffffff", 1785 | logoBackgroundTransparent: false, 1786 | 1787 | // === Posotion Pattern(Eye) Color 1788 | PO: undefined, // Global Posotion Outer color. if not set, the defaut is `colorDark` 1789 | PI: undefined, // Global Posotion Inner color. if not set, the defaut is `colorDark` 1790 | PO_TL: undefined, // Posotion Outer - Top Left 1791 | PI_TL: undefined, // Posotion Inner - Top Left 1792 | PO_TR: undefined, // Posotion Outer - Top Right 1793 | PI_TR: undefined, // Posotion Inner - Top Right 1794 | PO_BL: undefined, // Posotion Outer - Bottom Left 1795 | PI_BL: undefined, // Posotion Inner - Bottom Left 1796 | 1797 | // === Alignment Color 1798 | AO: undefined, // Alignment Outer. if not set, the defaut is `colorDark` 1799 | AI: undefined, // Alignment Inner. if not set, the defaut is `colorDark` 1800 | 1801 | // === Timing Pattern Color 1802 | timing: undefined, // Global Timing color. if not set, the defaut is `colorDark` 1803 | timing_H: undefined, // Horizontal timing color 1804 | timing_V: undefined, // Vertical timing color 1805 | 1806 | // ==== Backgroud Image 1807 | backgroundImage: undefined, // Background Image 1808 | backgroundImageAlpha: 1, // Background image transparency, value between 0 and 1. default is 1. 1809 | autoColor: false, // Automatic color adjustment(for data block) 1810 | autoColorDark: "rgba(0, 0, 0, .6)", // Automatic color: dark CSS color 1811 | autoColorLight: "rgba(255, 255, 255, .7)", // Automatic: color light CSS color 1812 | 1813 | // ==== Event Handler 1814 | onRenderingStart: undefined, 1815 | 1816 | // ==== Images format 1817 | format: "PNG", // 'PNG', 'JPG' 1818 | compressionLevel: 6, // ZLIB compression level (0-9). default is 6 1819 | quality: 0.75, // An object specifying the quality (0 to 1). default is 0.75. (JPGs only) 1820 | 1821 | // ==== Versions 1822 | version: 0, // The symbol versions of QR Code range from Version 1 to Version 40. default 0 means automatically choose the closest version based on the text length. 1823 | 1824 | // ==== binary(hex) data mode 1825 | binary: false, // Whether it is binary mode, default is text mode. 1826 | 1827 | // UTF-8 without BOM 1828 | utf8WithoutBOM: true, 1829 | }; 1830 | if (typeof vOption === "string") { 1831 | vOption = { 1832 | text: vOption, 1833 | }; 1834 | } 1835 | 1836 | // Overwrites options 1837 | if (vOption) { 1838 | for (var i in vOption) { 1839 | this._htOption[i] = vOption[i]; 1840 | } 1841 | } 1842 | 1843 | if (!this._htOption.title && !this._htOption.subTitle) { 1844 | this._htOption.titleHeight = 0; 1845 | } 1846 | if (this._htOption.version < 0 || this._htOption.version > 40) { 1847 | console.warn( 1848 | "QR Code version '" + 1849 | this._htOption.version + 1850 | "' is invalidate, reset to 0" 1851 | ); 1852 | this._htOption.version = 0; 1853 | } 1854 | 1855 | this._htOption.format = this._htOption.format.toUpperCase(); 1856 | if (this._htOption.format != "PNG" && this._htOption.format != "JPG") { 1857 | console.warn( 1858 | "Image format '" + 1859 | this._htOption.format + 1860 | "' is invalidate, reset to 'PNG'" 1861 | ); 1862 | this._htOption.format = "PNG"; 1863 | } 1864 | if ( 1865 | this._htOption.format == "PNG" && 1866 | (this._htOption.compressionLevel < 0 || this._htOption.compressionLevel > 9) 1867 | ) { 1868 | console.warn( 1869 | this._htOption.compressionLevel + 1870 | " is invalidate, PNG compressionLevel must between 0 and 9, now reset to 6. " 1871 | ); 1872 | this._htOption.compressionLevel = 1; 1873 | } else if (this._htOption.quality < 0 || this._htOption.quality > 1) { 1874 | console.warn( 1875 | this._htOption.quality + 1876 | " is invalidate, JPG quality must between 0 and 1, now reset to 0.75. " 1877 | ); 1878 | this._htOption.quality = 0.75; 1879 | } 1880 | 1881 | if (this._htOption.dotScale < 0 || this._htOption.dotScale > 1) { 1882 | console.warn( 1883 | this._htOption.dotScale + 1884 | " , is invalidate, dotScale must greater than 0, less than or equal to 1, now reset to 1. " 1885 | ); 1886 | this._htOption.dotScale = 1; 1887 | } 1888 | 1889 | if (this._htOption.dotScaleTiming < 0 || this._htOption.dotScaleTiming > 1) { 1890 | console.warn( 1891 | this._htOption.dotScaleTiming + 1892 | " , is invalidate, dotScaleTiming must greater than 0, less than or equal to 1, now reset to 1. " 1893 | ); 1894 | this._htOption.dotScaleTiming = 1; 1895 | } 1896 | if (this._htOption.dotScaleTiming_H) { 1897 | if ( 1898 | this._htOption.dotScaleTiming_H < 0 || 1899 | this._htOption.dotScaleTiming_H > 1 1900 | ) { 1901 | console.warn( 1902 | this._htOption.dotScaleTiming_H + 1903 | " , is invalidate, dotScaleTiming_H must greater than 0, less than or equal to 1, now reset to 1. " 1904 | ); 1905 | this._htOption.dotScaleTiming_H = 1; 1906 | } 1907 | } else { 1908 | this._htOption.dotScaleTiming_H = this._htOption.dotScaleTiming; 1909 | } 1910 | 1911 | if (this._htOption.dotScaleTiming_V) { 1912 | if ( 1913 | this._htOption.dotScaleTiming_V < 0 || 1914 | this._htOption.dotScaleTiming_V > 1 1915 | ) { 1916 | console.warn( 1917 | this._htOption.dotScaleTiming_V + 1918 | " , is invalidate, dotScaleTiming_V must greater than 0, less than or equal to 1, now reset to 1. " 1919 | ); 1920 | this._htOption.dotScaleTiming_V = 1; 1921 | } 1922 | } else { 1923 | this._htOption.dotScaleTiming_V = this._htOption.dotScaleTiming; 1924 | } 1925 | 1926 | if (this._htOption.dotScaleA < 0 || this._htOption.dotScaleA > 1) { 1927 | console.warn( 1928 | this._htOption.dotScaleA + 1929 | " , is invalidate, dotScaleA must greater than 0, less than or equal to 1, now reset to 1. " 1930 | ); 1931 | this._htOption.dotScaleA = 1; 1932 | } 1933 | if (this._htOption.dotScaleAO) { 1934 | if (this._htOption.dotScaleAO < 0 || this._htOption.dotScaleAO > 1) { 1935 | console.warn( 1936 | this._htOption.dotScaleAO + 1937 | " , is invalidate, dotScaleAO must greater than 0, less than or equal to 1, now reset to 1. " 1938 | ); 1939 | this._htOption.dotScaleAO = 1; 1940 | } 1941 | } else { 1942 | this._htOption.dotScaleAO = this._htOption.dotScaleA; 1943 | } 1944 | if (this._htOption.dotScaleAI) { 1945 | if (this._htOption.dotScaleAI < 0 || this._htOption.dotScaleAI > 1) { 1946 | console.warn( 1947 | this._htOption.dotScaleAI + 1948 | " , is invalidate, dotScaleAI must greater than 0, less than or equal to 1, now reset to 1. " 1949 | ); 1950 | this._htOption.dotScaleAI = 1; 1951 | } 1952 | } else { 1953 | this._htOption.dotScaleAI = this._htOption.dotScaleA; 1954 | } 1955 | 1956 | if ( 1957 | this._htOption.backgroundImageAlpha < 0 || 1958 | this._htOption.backgroundImageAlpha > 1 1959 | ) { 1960 | console.warn( 1961 | this._htOption.backgroundImageAlpha + 1962 | " , is invalidate, backgroundImageAlpha must between 0 and 1, now reset to 1. " 1963 | ); 1964 | this._htOption.backgroundImageAlpha = 1; 1965 | } 1966 | 1967 | if ( 1968 | !this._htOption.drawer || 1969 | (this._htOption.drawer != "svg" && this._htOption.drawer != "canvas") 1970 | ) { 1971 | this._htOption.drawer = "canvas"; 1972 | } 1973 | 1974 | // round 1975 | if (!this._htOption.quietZone) { 1976 | this._htOption.quietZone = 0; 1977 | } 1978 | if (!this._htOption.titleHeight) { 1979 | this._htOption.titleHeight = 0; 1980 | } 1981 | this._htOption.width = Math.round(this._htOption.width); 1982 | this._htOption.height = Math.round(this._htOption.height); 1983 | this._htOption.quietZone = Math.round(this._htOption.quietZone); 1984 | this._htOption.titleHeight = Math.round(this._htOption.titleHeight); 1985 | 1986 | this._oQRCode = null; 1987 | this._oQRCode = new QRCodeModel( 1988 | _getTypeNumber(this._htOption.text, this._htOption), 1989 | this._htOption.correctLevel 1990 | ); 1991 | this._oQRCode.addData( 1992 | this._htOption.text, 1993 | this._htOption.binary, 1994 | this._htOption.utf8WithoutBOM 1995 | ); 1996 | this._oQRCode.make(); 1997 | } 1998 | 1999 | // Save to image file or svg file 2000 | QRCode.prototype._toSave = function (saveOptions) { 2001 | var _oDrawing = new Drawing(Object.assign({}, this._htOption)); 2002 | _oDrawing.makeOptions = saveOptions; 2003 | 2004 | try { 2005 | var t = this; 2006 | return new Promise((resolve, reject) => { 2007 | _oDrawing.resolve = resolve; 2008 | _oDrawing.reject = reject; 2009 | _oDrawing.draw(t._oQRCode); 2010 | }); 2011 | } catch (e) { 2012 | console.error(e); 2013 | } 2014 | }; 2015 | 2016 | /** 2017 | * Support save PNG image file 2018 | * @param {Object} path Make the QRCode 2019 | */ 2020 | QRCode.prototype.saveImage = function (saveOptions) { 2021 | var defOptions = { 2022 | makeType: "FILE", 2023 | path: null, 2024 | }; 2025 | this._htOption._drawer = "canvas"; 2026 | saveOptions = Object.assign(defOptions, saveOptions); 2027 | return this._toSave(saveOptions); 2028 | }; 2029 | 2030 | /** 2031 | * Save to SVG file 2032 | */ 2033 | QRCode.prototype.saveSVG = function (saveOptions) { 2034 | var defOptions = { 2035 | makeType: "FILE", 2036 | path: null, 2037 | }; 2038 | this._htOption._drawer = "svg"; 2039 | saveOptions = Object.assign(defOptions, saveOptions); 2040 | return this._toSave(saveOptions); 2041 | }; 2042 | 2043 | // Get Base64, SVG text or Stream 2044 | QRCode.prototype._toData = function (drawer, makeType) { 2045 | var defOptions = { 2046 | makeType: makeType ? makeType : "URL", 2047 | }; 2048 | this._htOption._drawer = drawer; 2049 | 2050 | var _oDrawing = new Drawing(Object.assign({}, this._htOption)); 2051 | _oDrawing.makeOptions = defOptions; 2052 | 2053 | try { 2054 | var t = this; 2055 | return new Promise((resolve, reject) => { 2056 | _oDrawing.resolve = resolve; 2057 | _oDrawing.reject = reject; 2058 | _oDrawing.draw(t._oQRCode); 2059 | }); 2060 | } catch (e) { 2061 | console.error(e); 2062 | } 2063 | }; 2064 | 2065 | /** 2066 | *Get standard base64 image data url text: 'data:image/png;base64, ...' or SVG data text 2067 | */ 2068 | QRCode.prototype.toDataURL = function () { 2069 | return this._toData("canvas"); 2070 | }; 2071 | /** 2072 | * Get SVG data text: '>6,128|63&charcode):charcode<55296||57344<=charcode?utf8.push(224|charcode>>12,128|charcode>>6&63,128|63&charcode):(i++,charcode=65536+((1023&charcode)<<10|1023&str.charCodeAt(i)),utf8.push(240|charcode>>18,128|charcode>>12&63,128|charcode>>6&63,128|63&charcode))}return utf8}(data);this.parsedData=Array.prototype.concat.apply([],this.parsedData),utf8WithoutBOM||this.parsedData.length==this.data.length||(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function QRCodeModel(typeNumber,errorCorrectLevel){this.typeNumber=typeNumber,this.errorCorrectLevel=errorCorrectLevel,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}QR8bitByte.prototype={getLength:function(buffer){return this.parsedData.length},write:function(buffer){for(var i=0,l=this.parsedData.length;i>i&1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3][0]=mod}for(i=0;i<18;i++){mod=!test&&1==(bits>>i&1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)][0]=mod}},setupTypeInfo:function(test,maskPattern){for(var maskPattern=this.errorCorrectLevel<<3|maskPattern,bits=QRUtil.getBCHTypeInfo(maskPattern),i=0;i<15;i++){var mod=!test&&1==(bits>>i&1);i<6?this.modules[i][8][0]=mod:i<8?this.modules[i+1][8][0]=mod:this.modules[this.moduleCount-15+i][8][0]=mod}for(i=0;i<15;i++){mod=!test&&1==(bits>>i&1);i<8?this.modules[8][this.moduleCount-i-1][0]=mod:i<9?this.modules[8][15-i-1+1][0]=mod:this.modules[8][15-i-1][0]=mod}this.modules[this.moduleCount-8][8][0]=!test},mapData:function(data,maskPattern){for(var inc=-1,row=this.moduleCount-1,bitIndex=7,byteIndex=0,col=this.moduleCount-1;0>>bitIndex&1)),mask=QRUtil.getMask(maskPattern,row,col-c),this.modules[row][col-c][0]=dark=mask?!dark:dark,-1==--bitIndex)&&(byteIndex++,bitIndex=7);if((row+=inc)<0||this.moduleCount<=row){row-=inc,inc=-inc;break}}}},QRCodeModel.PAD0=236,QRCodeModel.PAD1=17,QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){for(var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel),buffer=new QRBitBuffer,i=0;i8*totalDataCount)throw new Error("code length overflow. ("+buffer.getLengthInBits()+">"+8*totalDataCount+")");for(buffer.getLengthInBits()+4<=8*totalDataCount&&buffer.put(0,4);buffer.getLengthInBits()%8!=0;)buffer.putBit(!1);for(;;){if(buffer.getLengthInBits()>=8*totalDataCount)break;if(buffer.put(QRCodeModel.PAD0,8),buffer.getLengthInBits()>=8*totalDataCount)break;buffer.put(QRCodeModel.PAD1,8)}return QRCodeModel.createBytes(buffer,rsBlocks)},QRCodeModel.createBytes=function(buffer,rsBlocks){for(var offset=0,maxDcCount=0,maxEcCount=0,dcdata=new Array(rsBlocks.length),ecdata=new Array(rsBlocks.length),r=0;r>>=1;return digit},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1]},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return i*j%2+i*j%3==0;case QRMaskPattern.PATTERN110:return(i*j%2+i*j%3)%2==0;case QRMaskPattern.PATTERN111:return(i*j%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern)}},getErrorCorrectPolynomial:function(errorCorrectLength){for(var a=new QRPolynomial([1],0),i=0;i>>7-index%8&1)},put:function(num,length){for(var i=0;i>>length-i-1&1))},getLengthInBits:function(){return this.length},putBit:function(bit){var bufIndex=Math.floor(this.length/8);this.buffer.length<=bufIndex&&this.buffer.push(0),bit&&(this.buffer[bufIndex]|=128>>>this.length%8),this.length++}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];function _getTypeNumber(sText,_htOption){for(var nCorrectLevel=_htOption.correctLevel,nType=1,length=_getUTF8Length(sText),i=0,len=QRCodeLimitLength.length;iQRCodeLimitLength.length)throw new Error("Too long data. the CorrectLevel."+["M","L","H","Q"][nCorrectLevel]+" limit length is "+nLimit);return 0!=_htOption.version&&(nType<=_htOption.version?nType=_htOption.version:console.warn("QR Code version "+_htOption.version+" too small, run version use "+nType),_htOption.runVersion=nType),nType}function _getUTF8Length(sText){var replacedText=encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return replacedText.length+(replacedText.length!=sText.length?3:0)}var Drawing=function(htOption){this._bIsPainted=!1,this._htOption=htOption,this._canvas=createCanvas(200,200),"svg"==this._htOption._drawer?this._oContext={}:this._oContext=this._canvas.getContext("2d"),this._bSupportDataURI=null};function QRCode(vOption){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRErrorCorrectLevel.H,dotScale:1,dotScaleTiming:1,dotScaleTiming_H:void 0,dotScaleTiming_V:void 0,dotScaleA:1,dotScaleAO:void 0,dotScaleAI:void 0,quietZone:0,quietZoneColor:"rgba(0,0,0,0)",title:"",titleFont:"normal normal bold 16px Arial",titleColor:"#000000",titleBackgroundColor:"#ffffff",titleHeight:0,titleTop:30,subTitle:"",subTitleFont:"normal normal normal 14px Arial",subTitleColor:"#4F4F4F",subTitleTop:60,logo:void 0,logoWidth:void 0,logoHeight:void 0,logoMaxWidth:void 0,logoMaxHeight:void 0,logoBackgroundColor:"#ffffff",logoBackgroundTransparent:!1,PO:void 0,PI:void 0,PO_TL:void 0,PI_TL:void 0,PO_TR:void 0,PI_TR:void 0,PO_BL:void 0,PI_BL:void 0,AO:void 0,AI:void 0,timing:void 0,timing_H:void 0,timing_V:void 0,backgroundImage:void 0,backgroundImageAlpha:1,autoColor:!1,autoColorDark:"rgba(0, 0, 0, .6)",autoColorLight:"rgba(255, 255, 255, .7)",onRenderingStart:void 0,format:"PNG",compressionLevel:6,quality:.75,version:0,binary:!1,utf8WithoutBOM:!0},vOption="string"==typeof vOption?{text:vOption}:vOption)for(var i in vOption)this._htOption[i]=vOption[i];this._htOption.title||this._htOption.subTitle||(this._htOption.titleHeight=0),(this._htOption.version<0||40{t.resolve({})}));else if("URL"==makeOptions.makeType)if(this._htOption.onRenderingStart&&this._htOption.onRenderingStart(this._htOption),"svg"==this._htOption._drawer){let data=this._oContext.getSerializedSvg();t.resolve(optimize(data).data)}else"PNG"==this._htOption.format?this._canvas.toDataURL((err,data)=>{t.resolve(data)}):this._canvas.toDataURL("image/jpeg",(err,data)=>{t.resolve(data)});else"STREAM"==makeOptions.makeType&&(this._htOption.onRenderingStart&&this._htOption.onRenderingStart(this._htOption),"PNG"==this._htOption.format?t.resolve(this._canvas.createPNGStream()):t.resolve(this._canvas.createJPEGStream()))},Drawing.prototype.isPainted=function(){return this._bIsPainted},Drawing.prototype.round=function(nNumber){return nNumber&&Math.floor(1e3*nNumber)/1e3},QRCode.prototype._toSave=function(saveOptions){var _oDrawing=new Drawing(Object.assign({},this._htOption));_oDrawing.makeOptions=saveOptions;try{var t=this;return new Promise((resolve,reject)=>{_oDrawing.resolve=resolve,_oDrawing.reject=reject,_oDrawing.draw(t._oQRCode)})}catch(e){console.error(e)}},QRCode.prototype.saveImage=function(saveOptions){return this._htOption._drawer="canvas",saveOptions=Object.assign({makeType:"FILE",path:null},saveOptions),this._toSave(saveOptions)},QRCode.prototype.saveSVG=function(saveOptions){return this._htOption._drawer="svg",saveOptions=Object.assign({makeType:"FILE",path:null},saveOptions),this._toSave(saveOptions)},QRCode.prototype._toData=function(drawer,makeType){var makeType={makeType:makeType||"URL"},_oDrawing=(this._htOption._drawer=drawer,new Drawing(Object.assign({},this._htOption)));_oDrawing.makeOptions=makeType;try{var t=this;return new Promise((resolve,reject)=>{_oDrawing.resolve=resolve,_oDrawing.reject=reject,_oDrawing.draw(t._oQRCode)})}catch(e){console.error(e)}},QRCode.prototype.toDataURL=function(){return this._toData("canvas")},QRCode.prototype.toSVGText=function(){return this._toData("svg")},QRCode.prototype.toStream=function(){return this._toData("canvas","STREAM")},QRCode.CorrectLevel=QRErrorCorrectLevel,module.exports=QRCode; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easyqrcodejs-nodejs", 3 | "version": "4.5.4", 4 | "description": "NodeJS QRCode generator. Can save image or svg to file, get standard base64 image data url text or get SVG serialized text. Cross-browser QRCode generator for pure javascript. Support Dot style, Logo, Background image, Colorful, Title etc. settings. support binary mode.(Running without DOM on server side)", 5 | "main": "index.min.js", 6 | "scripts": {}, 7 | "homepage": "https://github.com/ushelp/EasyQRCodeJS-NodeJS#readme", 8 | "author": "Ray ", 9 | "directories": { 10 | "dist": "dist", 11 | "src": "src", 12 | "demo": "demo" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ushelp/EasyQRCodeJS-NodeJS.git" 17 | }, 18 | "keywords": [ 19 | "qrcode", 20 | "qrcodejs", 21 | "canvas", 22 | "node", 23 | "nodejs", 24 | "easyqrcode node", 25 | "easyqrcodejs-nodejs", 26 | "easyqrcode nodejs", 27 | "qrcode node", 28 | "qrcode server", 29 | "qrcode server side", 30 | "qrcode nodejs", 31 | "javascript qrcode", 32 | "qrcode logo", 33 | "qrcode canvas", 34 | "node canvas", 35 | "node canvas qrcode", 36 | "node qrcode", 37 | "qrcode generator" 38 | ], 39 | "bugs": { 40 | "url": "https://github.com/ushelp/EasyQRCodeJS-NodeJS/issues " 41 | }, 42 | "license": "MIT", 43 | "dependencies": { 44 | "canvas": "^2.11.2", 45 | "jsdom": "^22.1.0", 46 | "svgo": "^3.0.2" 47 | }, 48 | "devDependencies": {} 49 | } 50 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # EasyQRCodeJS-NodeJS 2 | 3 | EasyQRCodeJS-NodeJS is a NodeJS server side javascript QRCode image(PNG/JPEG/SVG/Base64 data url) generator. Support setting Dot style, Logo, Background image, Colorful, Title and more. Support binary(hex) data mode. 4 | 5 | EasyQRCodeJS-NodeJS 是一个 NodeJS 环境下的服务端 JavaScript QRCode 图片(PNG/JPEG/SVG/Base64 data url)生成模块。支持点状风格,Logo,背景图片,规则色彩控制,标题等设置。支持二进制数据模式。 6 | 7 | ## Table of contents 8 | 9 | - [Table of contents](#table-of-contents) 10 | - [Choose what you need](#choose-what-you-need) 11 | - [Feature](#feature) 12 | - [Try It!](#try-it) 13 | - [Demo preview](#demo-preview) 14 | - [QR Code Structure](#qr-code-structure) 15 | - [Installation](#installation) 16 | - [Basic Usages](#basic-usages) 17 | - [QRCode API](#qrcode-api) 18 | - [Object](#object) 19 | - [Options](#options) 20 | - [Methods](#methods) 21 | - [TypeScript Support](#typescript-support) 22 | - [License](#license) 23 | - [EasyQRCodeJS-NodeJS-Premium](#easyqrcodejs-nodejs-premium) 24 | - [End](#end) 25 | 26 | ## Choose what you need 27 | 28 | | Project | Support | 29 | | --- | --- | 30 | | [EasyQRCodeJS](https://github.com/ushelp/EasyQRCodeJS) | **Running with DOM on CLIENT-SIDE .** Browser(IE6+, Chrome, Firefox, Safari, Opera, Mobile Safari, Android, Windows Mobile, ETC.), Electron, NW.js, ETC. | 31 | | [EasyQRCodeJS-NodeJS](https://github.com/ushelp/EasyQRCodeJS-NodeJS) | **Running without DOM on SERVER-SIDE**. Save image to file(PNG/JPEG/SVG) or get data url text. NodeJS, Electron, NW.js, ETC.| 32 | | [EasyQRCode-React-Native](https://github.com/ushelp/EasyQRCode-React-Native) | **A QRCode generator for React Native**. Generate QRCode image or get base64 data url text. | 33 | 34 | ## Feature 35 | 36 | - **English** 37 | 38 | - Save QRCode image file without DOM on server side 39 | 40 | - Support unicode character set: `😊❤️👍👨‍💻 Hello, こんにちは, こんにちは, Γεια, Привет, नमस्ते, สวัสดี, Привіт, سلام, Здравей, ສະບາຍດີ, Përshëndetje, Բարեւ, 你好` 41 | 42 | - Support save PNG/JPEG/SVG image file 43 | 44 | - Support get standard base64 image data url text: `data:image/png;base64, ...` 45 | 46 | - Support get SVG data text: ``QRCode.CorrectLevel.Q`
`QRCode.CorrectLevel.M`
`QRCode.CorrectLevel.L`| 284 | | Dot style| --- | ---|---|---| 285 | | **dotScale** | N | Number | `1.0` |Dot style scale. Ranges: `0-1.0` | 286 | | **dotScaleTiming** | N | Number | `1.0` |Dot style scale for timing. Ranges: `0-1.0` | 287 | | **dotScaleTiming_V** | N | Number | `undefined` |Dot style scale for horizontal timing. Ranges: `0-1.0` | 288 | | **dotScaleTiming_H** | N | Number | `undefined` |Dot style scale for vertical timing. Ranges: `0-1.0` | 289 | | **dotScaleA** | N | Number | `1.0` |Dot style scale for alignment. Ranges: `0-1.0` | 290 | | **dotScaleAO** | N | Number | `undefined` |Dot style scale for alignment outer. Ranges: `0-1.0` | 291 | | **dotScaleAI** | N | Number | `undefined` |Dot style scale for alignment inner. Ranges: `0-1.0` | 292 | | Quiet Zone| --- | ---|---|---| 293 | | **quietZone** | N | Number | `0` | Quiet Zone size | 294 | | **quietZoneColor** | N | String | `rgba(0,0,0,0)` | Background CSS color to Quiet Zone | 295 | | Logo options| --- | ---|---|---| 296 | | **logo** | N | String | `undefined` | Logo Image Path or Base64 encoded image. If use relative address, relative to `easy.qrcode.min.js` | 297 | | **logoWidth** | N | Number | `width/3.5` | Fixed logo width. | 298 | | **logoHeight** | N | Number | `height/3.5` | fixed logo height. | 299 | | **logoMaxWidth** | N | Number | `undefined` | Maximum logo width. if set will ignore `logoWidth` value. | 300 | | **logoMaxHeight** | N | Number | `undefined` | Maximum logo height. if set will ignore `logoHeight` value. | 301 | | **logoBackgroundTransparent** | N | Boolean | `false` | Whether the background transparent image(`PNG`) shows transparency. When `true`, `logoBackgroundColor` is invalid | 302 | | **logoBackgroundColor** | N | String | `#ffffff` | Set Background CSS Color when image background transparent. Valid when `logoBackgroundTransparent` is `false` | 303 | | Backgroud Image options| ---|--- |---|---| 304 | | **backgroundImage** | N | String | `undefined` | Background Image Path or Base64 encoded image. If use relative address, relative to `easy.qrcode.min.js` | 305 | | **backgroundImageAlpha** | N | Number | `1.0` | Background image transparency. Ranges: `0-1.0` | 306 | | **autoColor** | N | Boolean | `false` | Automatic color adjustment(for data block) | 307 | | **autoColorDark** | N | String | `rgba(0, 0, 0, .6)` | Automatic color: dark CSS color | 308 | | **autoColorLight** | N | String | `rgba(255, 255, 255, .7)` | Automatic color: light CSS color | 309 | | Posotion Pattern Color options| --- | ---|---|---| 310 | | **PO** | N | String | `undefined` | Global Posotion Outer CSS color. if not set, the defaut is `colorDark` | 311 | | **PI** | N | String | `undefined` | Global Posotion Inner CSS color. if not set, the defaut is `colorDark` | 312 | | **PO_TL** | N | String | `undefined` | Posotion Outer CSS color - Top Left | 313 | | **PI_TL** | N | String | `undefined` | Posotion Inner CSS color - Top Left | 314 | | **PO_TR** | N | String | `undefined` | Posotion Outer CSS color - Top Right | 315 | | **PI_TR** | N | String | `undefined` | Posotion Inner CSS color - Top Right | 316 | | **PO_BL** | N | String | `undefined` | Posotion Outer CSS color - Bottom Left | 317 | | **PI_BL** | N | String | `undefined` | Posotion Inner CSS color - Bottom Left | 318 | | Alignment Color options| --- |--- |---|---| 319 | | **AO** | N | String | `undefined` | Alignment Outer CSS color. if not set, the defaut is `colorDark` | 320 | | **AI** | N | String | `undefined` | Alignment Inner CSS color. if not set, the defaut is `colorDark` | 321 | | Timing Pattern Color options| --- | ---|---|---| 322 | | **timing** | N | String | `undefined` | Global Timing CSS color. if not set, the defaut is `colorDark` | 323 | | **timing_H** | N | String | `undefined` | Horizontal timing CSS color | 324 | | **timing_V** | N | String | `undefined` | Vertical timing CSS color | 325 | | Title options| --- | ---|---|---| 326 | | **title** | N | String | `''` | | 327 | | **titleFont** | N | String | `normal normal bold 16px Arial` | CSS Font | 328 | | **titleColor** | N | String | `#000000` | CSS color | 329 | | **titleBackgroundColor** | N | String | `#ffffff` | CSS color| 330 | | **titleHeight** | N | Number | `0` | Title Height, Include subTitle | 331 | | **titleTop** | N | Number | `30` | draws y coordinates.| 332 | | SubTitle options| --- | ---|---|---| 333 | | **subTitle** | N | String | `''` | | 334 | | **subTitleFont** | N | String | `normal normal normal 14px Arial` | CSS Font | 335 | | **subTitleColor** | N | String | `#4F4F4F` | CSS color | 336 | | **subTitleTop** | N | Number | `0` | draws y coordinates. default is 0| 337 | | Event Handler options| --- | ---|---|---| 338 | | **onRenderingStart(qrCodeOptions)** | N | Function | `undefined` | Callback function when rendering start work. can use to hide loading state or handling. | 339 | | Images format options| --- | ---|---|---| 340 | | **format** | N | String | `PNG` | 'PNG' or 'JPG' | 341 | | **compressionLevel** | N | Number | `6` | ZLIB compression level between 0 and 9. (**PNGs only**) | 342 | | **quality** | N | Number | `0.75` | An object specifying the quality (0 to 1). (**JPGs only**) | 343 | | Version options| --- | ---|---|---| 344 | | **version** | N | Number | `0` | The symbol versions of QR Code range from Version `1` to Version `40`. default 0 means automatically choose the closest version based on the text length. [Information capacity and versions of QR Codes](https://www.qrcode.com/en/about/version.html) **NOTE**: If you set a value less than the minimum version available for text, the minimum version is automatically used. | 345 | | UTF-8 options| --- | ---|---|---| 346 | | **utf8WithoutBOM** | N | Boolean | `true` | Use UTF-8 without BOM. set to `false` value will use BOM in UFT-8.| 347 | | Binary(hex) data model options| --- | ---|---|---| 348 | | **binary** | N | Boolean | `false` | Whether it is binary mode, default is text mode. | 349 | 350 | 351 | ### Methods 352 | 353 | - **saveImage(ImagesFormatOptions)** 354 | 355 | ```JS 356 | // Save PNG Images to file 357 | qrcode.saveImage({ 358 | path: 'q.png' // file path 359 | }).then(data=>{ 360 | console.log("`q-premium1.png` has been Created!"); 361 | }); 362 | ``` 363 | 364 | - **saveSVG(ImagesFormatOptions)** 365 | 366 | ```JS 367 | // Save SVG to file 368 | qrcode.saveSVG({ 369 | path: 'qrcode.svg' // file path 370 | }).then(data=>{ 371 | console.log("`qrcode.svg` has been Created!"); 372 | }); 373 | ``` 374 | 375 | - **toDataURL()** 376 | 377 | ```JS 378 | // Get standard base64 image data url text: 'data:image/png;base64, ...' 379 | qrcode.toDataURL().then(data=>{ 380 | console.info('======QRCode PNG Base64 DataURL======') 381 | console.info(data); 382 | }); 383 | ``` 384 | 385 | - **toSVGText()** 386 | 387 | ```JS 388 | // Get SVG data text: '{ 390 | console.info('======QRCode SVG Data Text======') 391 | console.info(data) 392 | }); 393 | ``` 394 | 395 | - **toStream()** 396 | 397 | ```JS 398 | const out = fs.createWriteStream(`qrcode-stream.png`); 399 | // const stream = await qrcode.toStream(); 400 | // stream.pipe(out); 401 | // out.on('finish', () => console.log('Finsihed')); 402 | 403 | qrcode.toStream().then(res=>{ 404 | res.pipe(out).on('finish', () => console.log('Finsihed')); 405 | }) 406 | ``` 407 | 408 | ## TypeScript Support 409 | 410 | Update to version `3.7.1+`. 411 | 412 | ```Javascript 413 | import QRCode = require("easyqrcodejs-nodejs") 414 | ``` 415 | 416 | ## License 417 | MIT License 418 | 419 | ## EasyQRCodeJS-NodeJS-Premium 420 | 421 | *Let you draw freely!* 422 | 423 | **EasyQRCodeJS-Premium** is a more powerful and comprehensive enterprise version. You can use Canvas to customize any element, such as eye frame shape, eyeball shape, QR code block shape, and more. Also supports excavation (to prevent the QRcode overlap with the logo), random block mode. 424 | 425 | If you need more functions, we can provide you with customized development of API libraries or products. please contact me to buy the business enterprise edition. 426 | 427 | **EasyQRCodeJS-Premium** 是功能更加强大和全面的商业/企业版本。让您可以在 QRCode 中通过 Canvas 自定义任何喜欢的元素,例如 Eye frame 形状, Eye ball 形状, QR Body block 形状等等。 还支持 Logo 挖取(excavation,防止二维码与 Logo 重叠)和 Random bolock mode. 428 | 429 | 如果您需要更多功能,我们可以为您提供 API 库或产品的定制开发。请联系我购买商业/企业版本。 430 | 431 | ![Premium demo preview](doc/images/demo-premium.png) 432 | 433 | ## End 434 | 435 | Email: 436 | 437 | [http://www.easyproject.cn](http://www.easyproject.cn "EasyProject Home") 438 | 439 | 440 | **Donation/捐助:** 441 | 442 | 443 | 
444 | 支付宝/微信/QQ/云闪付/PayPal 扫码支付 445 |
支付宝/微信/QQ/云闪付/PayPal
446 | 447 |
448 | 449 | 我们相信,每个人的点滴贡献,都将是推动产生更多、更好免费开源产品的一大步。 450 | 451 | **感谢慷慨捐助,以支持服务器运行和鼓励更多社区成员。** 452 | 453 | We believe that the contribution of each bit by bit, will be driven to produce more and better free and open source products a big step. 454 | 455 | **Thank you donation to support the server running and encourage more community members.** 456 | 457 | 458 | --------------------------------------------------------------------------------