├── .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 |
--------------------------------------------------------------------------------
/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: '