@liquid-js/qr-code-styling 5.6.1 → 5.7.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -135,10 +135,12 @@ let ErrorCorrectionPercents = {
135
135
  let rx = /\.?0+$/;
136
136
 
137
137
  function numToAttr(value) {
138
- return "string" == typeof value ? value : value.toFixed(7).replace(rx, "");
138
+ if ("string" == typeof value) return value;
139
+ let str = value.toFixed(7).replace(rx, "");
140
+ return "-0" == str ? "0" : str;
139
141
  }
140
142
 
141
- function svgPath(strings, ...values) {
143
+ function svgAttr(strings, ...values) {
142
144
  return strings.reduce((t, v, i) => {
143
145
  let p = "";
144
146
  return i && (p = values[i - 1]), "number" == typeof p && (p = numToAttr(p)), `${t}${p}${v}`;
@@ -147,10 +149,125 @@ function svgPath(strings, ...values) {
147
149
 
148
150
  function rotateFigure({draw, ...args}) {
149
151
  let {x, y, size, rotation = 0} = args, cx = x + size / 2, cy = y + size / 2, element = draw(args);
150
- return rotation && (Array.isArray(element) ? element : [ element ]).forEach(el => el.setAttribute("transform", `rotate(${numToAttr(180 * rotation / Math.PI)},${cx},${cy})`)),
152
+ return rotation && (Array.isArray(element) ? element : [ element ]).forEach(el => el.setAttribute("transform", svgAttr`rotate(${180 * rotation / Math.PI},${cx},${cy})`)),
151
153
  element;
152
154
  }
153
155
 
156
+ let paramCount = {
157
+ m: 2,
158
+ l: 2,
159
+ h: 1,
160
+ v: 1,
161
+ c: 6,
162
+ s: 4,
163
+ q: 4,
164
+ t: 2,
165
+ a: 7,
166
+ z: 0
167
+ };
168
+
169
+ function rotatePath(d, angle = 0, cx = 0, cy = 0) {
170
+ let segments = function(d) {
171
+ let segments = [], skipRx = /[\s,]*/y, cmdRx = /[MmLlHhVvCcSsQqTtAaZz]/y, numRx = /[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/y, flagRx = /[01]/y, pos = 0, match = re => {
172
+ skipRx.lastIndex = pos, skipRx.exec(d), pos = re.lastIndex = skipRx.lastIndex;
173
+ const m = re.exec(d);
174
+ return m && (pos = re.lastIndex), m?.[0];
175
+ }, cmd = "";
176
+ for (;;) {
177
+ let c = match(cmdRx);
178
+ if (c) cmd = c; else {
179
+ if (pos >= d.length) break;
180
+ if (!cmd || "z" == cmd || "Z" == cmd) return;
181
+ "M" == cmd && (cmd = "L"), "m" == cmd && (cmd = "l");
182
+ }
183
+ let isArc = "a" == cmd || "A" == cmd, args = [];
184
+ for (let i = 0; i < paramCount[cmd.toLowerCase()]; i++) {
185
+ let v = match(!isArc || 3 != i && 4 != i ? numRx : flagRx);
186
+ if (void 0 === v) return;
187
+ args.push(parseFloat(v));
188
+ }
189
+ segments.push([ cmd, args ]);
190
+ }
191
+ return segments;
192
+ }(d);
193
+ if (!segments) return;
194
+ let rad = angle * Math.PI / 180, cos = Math.cos(rad), sin = Math.sin(rad), vector = (dx, dy) => svgAttr`${dx * cos - dy * sin} ${dx * sin + dy * cos}`, point = (px, py) => svgAttr`${cx + (px - cx) * cos - (py - cy) * sin} ${cy + (px - cx) * sin + (py - cy) * cos}`, x = 0, y = 0, startX = 0, startY = 0;
195
+ return segments.map(([cmd, args], i) => {
196
+ 0 == i && "m" == cmd && (cmd = "M");
197
+ let rel = cmd != cmd.toUpperCase(), map = rel ? vector : point, ox = rel ? x : 0, oy = rel ? y : 0;
198
+ switch (cmd.toUpperCase()) {
199
+ case "Z":
200
+ return x = startX, y = startY, cmd;
201
+
202
+ case "H":
203
+ case "V":
204
+ {
205
+ let isH = "H" == cmd.toUpperCase(), dx = isH ? args[0] - (rel ? 0 : x) : 0, dy = isH ? 0 : args[0] - (rel ? 0 : y);
206
+ return x += dx, y += dy, angle ? svgAttr`l ${vector(dx, dy)}` : svgAttr`${cmd} ${args[0]}`;
207
+ }
208
+
209
+ case "A":
210
+ return x = ox + args[5], y = oy + args[6], svgAttr`${cmd} ${args[0]} ${args[1]} ${args[2] + angle} ${args[3]} ${args[4]} ${map(args[5], args[6])}`;
211
+
212
+ default:
213
+ {
214
+ let points = [];
215
+ for (let j = 0; j < args.length; j += 2) points.push(map(args[j], args[j + 1]));
216
+ return x = ox + args[args.length - 2], y = oy + args[args.length - 1], "M" != cmd && "m" != cmd || (startX = x,
217
+ startY = y), svgAttr`${cmd} ${points.join(" ")}`;
218
+ }
219
+ }
220
+ }).join(" ");
221
+ }
222
+
223
+ function shapeToPath(el) {
224
+ let num = name => parseFloat(el.getAttribute(name) || "0") || 0;
225
+ switch (el.tagName) {
226
+ case "path":
227
+ return el.getAttribute("d") || "";
228
+
229
+ case "rect":
230
+ {
231
+ let x = num("x"), y = num("y"), w = num("width"), h = num("height"), rX = el.hasAttribute("rx") ? num("rx") : num("ry"), rY = el.hasAttribute("ry") ? num("ry") : rX;
232
+ return rX = Math.min(Math.max(rX, 0), w / 2), rY = Math.min(Math.max(rY, 0), h / 2),
233
+ w <= 0 || h <= 0 ? "" : rX && rY ? svgAttr`M ${x} ${y + rY} v ${h - 2 * rY} a ${rX} ${rY} 0 0 0 ${rX} ${rY} h ${w - 2 * rX} a ${rX} ${rY} 0 0 0 ${rX} ${-rY} v ${2 * rY - h} a ${rX} ${rY} 0 0 0 ${-rX} ${-rY} h ${2 * rX - w} a ${rX} ${rY} 0 0 0 ${-rX} ${rY} z` : svgAttr`M ${x} ${y} v ${h} h ${w} v ${-h} z`;
234
+ }
235
+
236
+ case "circle":
237
+ case "ellipse":
238
+ {
239
+ let cx = num("cx"), cy = num("cy"), rX = "circle" == el.tagName ? num("r") : num("rx"), rY = "circle" == el.tagName ? rX : num("ry");
240
+ return rX <= 0 || rY <= 0 ? "" : svgAttr`M ${cx - rX} ${cy} a ${rX} ${rY} 0 1 0 ${2 * rX} 0 a ${rX} ${rY} 0 1 0 ${-2 * rX} 0 z`;
241
+ }
242
+
243
+ case "polygon":
244
+ return `M ${el.getAttribute("points") || ""} z`;
245
+
246
+ default:
247
+ return;
248
+ }
249
+ }
250
+
251
+ function mergeShapes(elements) {
252
+ let rotateRx = /^\s*rotate\(\s*([-+.\deE]+)(?:[\s,]+([-+.\deE]+)[\s,]+([-+.\deE]+))?\s*\)\s*$/, paths = {};
253
+ for (let el of elements) {
254
+ if (el.hasAttribute("clip-path")) return;
255
+ let d = shapeToPath(el);
256
+ if (void 0 === d) return;
257
+ if (!d) continue;
258
+ let transform = el.getAttribute("transform"), m = transform ? rotateRx.exec(transform) : void 0;
259
+ if (null === m) return;
260
+ let rotated = m ? rotatePath(d, parseFloat(m[1]), parseFloat(m[2] || "0"), parseFloat(m[3] || "0")) : rotatePath(d);
261
+ if (void 0 === rotated) return;
262
+ let rule = "evenodd" == el.getAttribute("clip-rule") ? "evenodd" : "nonzero";
263
+ paths[rule] || (paths[rule] = []), paths[rule].push(rotated);
264
+ }
265
+ return Object.entries(paths).map(([rule, d]) => ({
266
+ rule,
267
+ d: d.join(" ")
268
+ }));
269
+ }
270
+
154
271
  let DotElements = {
155
272
  dot: args => {
156
273
  const {size, x, y, document} = args, element = document.createElementNS("http://www.w3.org/2000/svg", "circle");
@@ -165,27 +282,27 @@ let DotElements = {
165
282
  },
166
283
  sideRounded: (args, {height = args.size} = {}) => {
167
284
  const {size, x, y, document} = args, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
168
- return element.setAttribute("d", svgPath`M ${x} ${y + (size - height) / 2} v ${height} h ${size / 2} a ${height / 2} ${height / 2} 0 0 0 0 ${-height} z`),
285
+ return element.setAttribute("d", svgAttr`M ${x} ${y + (size - height) / 2} v ${height} h ${size / 2} a ${height / 2} ${height / 2} 0 0 0 0 ${-height} z`),
169
286
  element;
170
287
  },
171
288
  cornerRounded: args => {
172
289
  const {size, x, y, document} = args, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
173
- return element.setAttribute("d", svgPath`M ${x} ${y} v ${size} h ${size} v ${-size / 2} a ${size / 2} ${size / 2} 0 0 0 ${-size / 2} ${-size / 2} z`),
290
+ return element.setAttribute("d", svgAttr`M ${x} ${y} v ${size} h ${size} v ${-size / 2} a ${size / 2} ${size / 2} 0 0 0 ${-size / 2} ${-size / 2} z`),
174
291
  element;
175
292
  },
176
293
  cornerExtraRounded: args => {
177
294
  const {size, x, y, document} = args, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
178
- return element.setAttribute("d", svgPath`M ${x} ${y} v ${size} h ${size} a ${size} ${size} 0 0 0 ${-size} ${-size} z`),
295
+ return element.setAttribute("d", svgAttr`M ${x} ${y} v ${size} h ${size} a ${size} ${size} 0 0 0 ${-size} ${-size} z`),
179
296
  element;
180
297
  },
181
298
  classyDot: args => {
182
299
  const {size, x, y, document} = args, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
183
- return element.setAttribute("d", svgPath`M ${x} ${y} v ${size / 2} a ${size / 2} ${size / 2} 0 0 0 ${size / 2} ${size / 2} h ${size / 2} v ${-size / 2} a ${size / 2} ${size / 2} 0 0 0 ${-size / 2} ${-size / 2} z`),
300
+ return element.setAttribute("d", svgAttr`M ${x} ${y} v ${size / 2} a ${size / 2} ${size / 2} 0 0 0 ${size / 2} ${size / 2} h ${size / 2} v ${-size / 2} a ${size / 2} ${size / 2} 0 0 0 ${-size / 2} ${-size / 2} z`),
184
301
  element;
185
302
  },
186
303
  wave: args => {
187
304
  const {size, x, y, document} = args, a1 = 5 * Math.PI / 180, a2 = 65 * Math.PI / 180, c23x = size * (1 - .65 * Math.sin(a1)), c23y = -size * (.95 - .65 * Math.cos(a1)), c31x = -size * Math.sin(a2) / 2, c31y = size * Math.cos(a2) / 2, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
188
- return element.setAttribute("d", svgPath`M ${x} ${y + size} c ${.65 * size} 0 ${c23x} ${c23y} ${size} ${.95 * -size} c ${c31x} ${c31y} ${-size / 2} ${size * (.95 - 1)} ${-size} ${size * (.95 - 1)} z`),
305
+ return element.setAttribute("d", svgAttr`M ${x} ${y + size} c ${.65 * size} 0 ${c23x} ${c23y} ${size} ${.95 * -size} c ${c31x} ${c31y} ${-size / 2} ${size * (.95 - 1)} ${-size} ${size * (.95 - 1)} z`),
189
306
  element;
190
307
  },
191
308
  heart: args => {
@@ -209,44 +326,44 @@ let DotElements = {
209
326
  },
210
327
  classy: args => {
211
328
  const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
212
- return element.setAttribute("d", svgPath`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${4.5 * dotSize} v ${-4.5 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} H ${x} z`),
329
+ return element.setAttribute("d", svgAttr`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${4.5 * dotSize} v ${-4.5 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} H ${x} z`),
213
330
  element;
214
331
  },
215
332
  inpoint: args => {
216
333
  const {size, x, y, document} = args, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
217
- return element.setAttribute("d", svgPath`M ${x} ${y + size / 2} v ${size / 4} a ${size / 4} ${size / 4} 0 0 0 ${size / 4} ${size / 4} h ${size / 4 * 3} v ${-size / 4 * 3} a ${size / 4} ${size / 4} 0 0 0 ${-size / 4} ${-size / 4} h ${-size / 2} a ${size / 4} ${size / 4} 0 0 0 ${-size / 4} ${size / 4} z`),
334
+ return element.setAttribute("d", svgAttr`M ${x} ${y + size / 2} v ${size / 4} a ${size / 4} ${size / 4} 0 0 0 ${size / 4} ${size / 4} h ${size / 4 * 3} v ${-size / 4 * 3} a ${size / 4} ${size / 4} 0 0 0 ${-size / 4} ${-size / 4} h ${-size / 2} a ${size / 4} ${size / 4} 0 0 0 ${-size / 4} ${size / 4} z`),
218
335
  element;
219
336
  },
220
337
  star: (args, {spikes = 5, outerRadius = args.size / 2, innerRadius = args.size / 4} = {}) => {
221
338
  const {size, x, y, document} = args, cx = x + size / 2, cy = y + size / 2, angle = Math.PI / spikes, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
222
339
  return element.setAttribute("d", Array(2 * spikes).fill(0).map((_, i) => {
223
340
  const radius = i % 2 == 0 ? outerRadius : innerRadius;
224
- return svgPath`${0 === i ? "M" : "L"} ${cx + radius * Math.sin(i * angle)} ${cy - radius * Math.cos(i * angle)}`;
341
+ return svgAttr`${0 === i ? "M" : "L"} ${cx + radius * Math.sin(i * angle)} ${cy - radius * Math.cos(i * angle)}`;
225
342
  }).join(" ")), element;
226
343
  },
227
344
  weave: args => {
228
345
  const {size, x, y, document} = args, thickness = .6 * size, notch = (size - thickness) / 2, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
229
- return element.setAttribute("d", svgPath`M ${x + notch} ${y} l ${thickness} 0 l 0 ${notch} l ${notch} 0 l 0 ${thickness} l ${-notch} 0 l 0 ${notch} l ${-thickness} 0 l 0 ${-notch} l ${-notch} 0 l 0 ${-thickness} l ${notch} 0 z`),
346
+ return element.setAttribute("d", svgAttr`M ${x + notch} ${y} l ${thickness} 0 l 0 ${notch} l ${notch} 0 l 0 ${thickness} l ${-notch} 0 l 0 ${notch} l ${-thickness} 0 l 0 ${-notch} l ${-notch} 0 l 0 ${-thickness} l ${notch} 0 z`),
230
347
  element;
231
348
  },
232
349
  blob: (args, corners) => {
233
- const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path"), parts = [ svgPath`M ${x} ${y + 2.5 * dotSize}` ];
350
+ const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path"), parts = [ svgAttr`M ${x} ${y + 2.5 * dotSize}` ];
234
351
  let l = 2 * dotSize;
235
- return "round" == corners.bl ? (parts.push(svgPath`v ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize}`),
236
- l = 2 * dotSize) : "out" == corners.bl ? (parts.push(svgPath`v ${5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${2.5 * dotSize} ${-2.5 * dotSize}`),
237
- l = 2 * dotSize) : (parts.push(svgPath`v ${2.5 * dotSize + l}`), l = 4.5 * dotSize),
238
- "round" == corners.br ? (parts.push(svgPath`h ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${-2.5 * dotSize}`),
239
- l = -2 * dotSize) : "out" == corners.br ? (parts.push(svgPath`h ${5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${-2.5 * dotSize} ${-2.5 * dotSize}`),
240
- l = -2 * dotSize) : (parts.push(svgPath`h ${2.5 * dotSize + l}`), l = -4.5 * dotSize),
241
- "round" == corners.tr ? (parts.push(svgPath`v ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${-2.5 * dotSize} ${-2.5 * dotSize}`),
242
- l = -2 * dotSize) : "out" == corners.tr ? (parts.push(svgPath`v ${-5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${-2.5 * dotSize} ${2.5 * dotSize}`),
243
- l = -2 * dotSize) : (parts.push(svgPath`v ${-2.5 * dotSize + l}`), l = -4.5 * dotSize),
244
- "round" == corners.tl ? parts.push(svgPath`h ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${-2.5 * dotSize} ${2.5 * dotSize}`) : "out" == corners.tl ? parts.push(svgPath`h ${-5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${2.5 * dotSize} ${2.5 * dotSize}`) : parts.push(svgPath`h ${-2.5 * dotSize + l}`),
352
+ return "round" == corners.bl ? (parts.push(svgAttr`v ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize}`),
353
+ l = 2 * dotSize) : "out" == corners.bl ? (parts.push(svgAttr`v ${5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${2.5 * dotSize} ${-2.5 * dotSize}`),
354
+ l = 2 * dotSize) : (parts.push(svgAttr`v ${2.5 * dotSize + l}`), l = 4.5 * dotSize),
355
+ "round" == corners.br ? (parts.push(svgAttr`h ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${-2.5 * dotSize}`),
356
+ l = -2 * dotSize) : "out" == corners.br ? (parts.push(svgAttr`h ${5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${-2.5 * dotSize} ${-2.5 * dotSize}`),
357
+ l = -2 * dotSize) : (parts.push(svgAttr`h ${2.5 * dotSize + l}`), l = -4.5 * dotSize),
358
+ "round" == corners.tr ? (parts.push(svgAttr`v ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${-2.5 * dotSize} ${-2.5 * dotSize}`),
359
+ l = -2 * dotSize) : "out" == corners.tr ? (parts.push(svgAttr`v ${-5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${-2.5 * dotSize} ${2.5 * dotSize}`),
360
+ l = -2 * dotSize) : (parts.push(svgAttr`v ${-2.5 * dotSize + l}`), l = -4.5 * dotSize),
361
+ "round" == corners.tl ? parts.push(svgAttr`h ${l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${-2.5 * dotSize} ${2.5 * dotSize}`) : "out" == corners.tl ? parts.push(svgAttr`h ${-5 * dotSize + l} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 1 ${2.5 * dotSize} ${2.5 * dotSize}`) : parts.push(svgAttr`h ${-2.5 * dotSize + l}`),
245
362
  element.setAttribute("d", parts.join(" ") + " z"), element;
246
363
  },
247
364
  circuit: (args, left = !1) => {
248
365
  const {size, x, y, document} = args, dP = .17 ** .5 * size, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
249
- return element.setAttribute("d", svgPath`M ${x + .4 * size} ${y + .5 * size - dP} a ${.4 * size} ${.4 * size} 0 0 0 ${left ? .1 * size - dP : -.3 * size} ${left ? dP - .1 * size : dP} ${left ? svgPath`h ${-.65 * size} v ${.2 * size} h ${.65 * size} a ${.4 * size} ${.4 * size} 0 0 0 ${dP} ${.3 * size}` : svgPath`a ${.4 * size} ${.4 * size} 0 0 0 ${.4 * size} ${.4 * size}`} a ${.4 * size} ${.4 * size} 0 0 0 ${.4 * size} ${-.4 * size} a ${.4 * size} ${.4 * size} 0 0 0 ${-.3 * size} ${-dP} v ${-.65 * size} h ${-.2 * size} z`),
366
+ return element.setAttribute("d", svgAttr`M ${x + .4 * size} ${y + .5 * size - dP} a ${.4 * size} ${.4 * size} 0 0 0 ${left ? .1 * size - dP : -.3 * size} ${left ? dP - .1 * size : dP} ${left ? svgAttr`h ${-.65 * size} v ${.2 * size} h ${.65 * size} a ${.4 * size} ${.4 * size} 0 0 0 ${dP} ${.3 * size}` : svgAttr`a ${.4 * size} ${.4 * size} 0 0 0 ${.4 * size} ${.4 * size}`} a ${.4 * size} ${.4 * size} 0 0 0 ${.4 * size} ${-.4 * size} a ${.4 * size} ${.4 * size} 0 0 0 ${-.3 * size} ${-dP} v ${-.65 * size} h ${-.2 * size} z`),
250
367
  element.setAttribute("fill-rule", "nonzero"), element;
251
368
  }
252
369
  }, qrCornerDotFigures = {
@@ -305,44 +422,44 @@ let DotElements = {
305
422
  let SquareElements = {
306
423
  dot: args => {
307
424
  const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
308
- element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgPath`M ${x + size / 2} ${y} a ${size / 2} ${size / 2} 0 1 0 0.1 0 z m 0 ${dotSize} a ${size / 2 - dotSize} ${size / 2 - dotSize} 0 1 1 -0.1 0 Z`);
425
+ element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgAttr`M ${x + size / 2} ${y} a ${size / 2} ${size / 2} 0 1 0 0.1 0 z m 0 ${dotSize} a ${size / 2 - dotSize} ${size / 2 - dotSize} 0 1 1 -0.1 0 Z`);
309
426
  const fill = document.createElementNS("http://www.w3.org/2000/svg", "path");
310
- return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgPath`M ${x + size / 2} ${y - dotSize} a ${size / 2 + dotSize} ${size / 2 + dotSize} 0 1 0 0.1 0 z`),
427
+ return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgAttr`M ${x + size / 2} ${y - dotSize} a ${size / 2 + dotSize} ${size / 2 + dotSize} 0 1 0 0.1 0 z`),
311
428
  [ element, fill ];
312
429
  },
313
430
  square: args => {
314
431
  const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
315
- element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgPath`M ${x} ${y} v ${size} h ${size} v ${-size} z M ${x + dotSize} ${y + dotSize} h ${size - 2 * dotSize} v ${size - 2 * dotSize} h ${2 * dotSize - size} z`);
432
+ element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgAttr`M ${x} ${y} v ${size} h ${size} v ${-size} z M ${x + dotSize} ${y + dotSize} h ${size - 2 * dotSize} v ${size - 2 * dotSize} h ${2 * dotSize - size} z`);
316
433
  const fill = document.createElementNS("http://www.w3.org/2000/svg", "path");
317
- return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgPath`M ${x - dotSize} ${y - dotSize} h ${size + 2 * dotSize} v ${size + 2 * dotSize} h ${-size - 2 * dotSize} z`),
434
+ return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgAttr`M ${x - dotSize} ${y - dotSize} h ${size + 2 * dotSize} v ${size + 2 * dotSize} h ${-size - 2 * dotSize} z`),
318
435
  [ element, fill ];
319
436
  },
320
437
  extraRounded: args => {
321
438
  const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
322
- element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgPath`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * -dotSize} v ${-2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * dotSize} z M ${x + 2.5 * dotSize} ${y + dotSize} h ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * dotSize} v ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * dotSize} h ${-2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * -dotSize} v ${-2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * -dotSize} z`);
439
+ element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgAttr`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * -dotSize} v ${-2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * dotSize} z M ${x + 2.5 * dotSize} ${y + dotSize} h ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * dotSize} v ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * dotSize} h ${-2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * -dotSize} v ${-2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * -dotSize} z`);
323
440
  const fill = document.createElementNS("http://www.w3.org/2000/svg", "path");
324
- return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgPath`M ${x - dotSize} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * dotSize} ${3.5 * dotSize} h ${2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * dotSize} ${3.5 * -dotSize} v ${-2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * -dotSize} ${3.5 * -dotSize} h ${-2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * -dotSize} ${3.5 * dotSize} z`),
441
+ return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgAttr`M ${x - dotSize} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * dotSize} ${3.5 * dotSize} h ${2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * dotSize} ${3.5 * -dotSize} v ${-2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * -dotSize} ${3.5 * -dotSize} h ${-2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 0 ${3.5 * -dotSize} ${3.5 * dotSize} z`),
325
442
  [ element, fill ];
326
443
  },
327
444
  classy: args => {
328
445
  const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
329
- element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgPath`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${4.5 * dotSize} v ${-4.5 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} H ${x} z M ${x + 2.5 * dotSize} ${y + dotSize} h ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * dotSize} v ${3.5 * dotSize} h ${-3.5 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * -dotSize} v ${-3.5 * dotSize} z`);
446
+ element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgAttr`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${4.5 * dotSize} v ${-4.5 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} H ${x} z M ${x + 2.5 * dotSize} ${y + dotSize} h ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * dotSize} v ${3.5 * dotSize} h ${-3.5 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * -dotSize} v ${-3.5 * dotSize} z`);
330
447
  const fill = document.createElementNS("http://www.w3.org/2000/svg", "path");
331
- return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgPath`M ${x + .5 * dotSize} ${y - dotSize} h ${4 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * dotSize} ${3.5 * dotSize} v ${5.5 * dotSize} h ${-5.5 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * -dotSize} ${3.5 * -dotSize} v ${-5.5 * dotSize} z`),
448
+ return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgAttr`M ${x + .5 * dotSize} ${y - dotSize} h ${4 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * dotSize} ${3.5 * dotSize} v ${5.5 * dotSize} h ${-5.5 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * -dotSize} ${3.5 * -dotSize} v ${-5.5 * dotSize} z`),
332
449
  [ element, fill ];
333
450
  },
334
451
  inpoint: args => {
335
452
  const {size, x, y, document} = args, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
336
- element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgPath`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${4.5 * dotSize} v ${-4.5 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * dotSize} z M ${x + 2.5 * dotSize} ${y + dotSize} h ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * dotSize} v ${3.5 * dotSize} h ${-3.5 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * -dotSize} v ${-2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * -dotSize} z`);
453
+ element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgAttr`M ${x} ${y + 2.5 * dotSize} v ${2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * dotSize} ${2.5 * dotSize} h ${4.5 * dotSize} v ${-4.5 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * -dotSize} h ${-2 * dotSize} a ${2.5 * dotSize} ${2.5 * dotSize} 0 0 0 ${2.5 * -dotSize} ${2.5 * dotSize} z M ${x + 2.5 * dotSize} ${y + dotSize} h ${2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * dotSize} v ${3.5 * dotSize} h ${-3.5 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * -dotSize} ${1.5 * -dotSize} v ${-2 * dotSize} a ${1.5 * dotSize} ${1.5 * dotSize} 0 0 1 ${1.5 * dotSize} ${1.5 * -dotSize} z`);
337
454
  const fill = document.createElementNS("http://www.w3.org/2000/svg", "path");
338
- return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgPath`M ${x + .5 * dotSize} ${y - dotSize} h ${4 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * dotSize} ${3.5 * dotSize} v ${5.5 * dotSize} h ${-5.5 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * -dotSize} ${3.5 * -dotSize} v ${-2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * dotSize} ${3.5 * -dotSize} z`),
455
+ return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgAttr`M ${x + .5 * dotSize} ${y - dotSize} h ${4 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * dotSize} ${3.5 * dotSize} v ${5.5 * dotSize} h ${-5.5 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * -dotSize} ${3.5 * -dotSize} v ${-2 * dotSize} a ${3.5 * dotSize} ${3.5 * dotSize} 0 0 1 ${3.5 * dotSize} ${3.5 * -dotSize} z`),
339
456
  [ element, fill ];
340
457
  },
341
458
  centerCircle: args => {
342
459
  const {x, y, size, document} = args, circleRadius = size / 2.5, cx = x + size / 2, cy = y + size / 2, dotSize = size / 7, element = document.createElementNS("http://www.w3.org/2000/svg", "path");
343
- element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgPath`M ${x} ${y} h ${size} v ${size} h ${-size} z M ${cx - circleRadius} ${cy} a ${circleRadius} ${circleRadius} 0 1 0 ${2 * circleRadius} 0 a ${circleRadius} ${circleRadius} 0 1 0 ${2 * -circleRadius} 0 z`);
460
+ element.setAttribute("clip-rule", "evenodd"), element.setAttribute("d", svgAttr`M ${x} ${y} h ${size} v ${size} h ${-size} z M ${cx - circleRadius} ${cy} a ${circleRadius} ${circleRadius} 0 1 0 ${2 * circleRadius} 0 a ${circleRadius} ${circleRadius} 0 1 0 ${2 * -circleRadius} 0 z`);
344
461
  const fill = document.createElementNS("http://www.w3.org/2000/svg", "path");
345
- return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgPath`M ${x - dotSize} ${y - dotSize} h ${size + 2 * dotSize} v ${size + 2 * dotSize} h ${-size - 2 * dotSize} z`),
462
+ return fill.setAttribute("clip-rule", "evenodd"), fill.setAttribute("d", svgAttr`M ${x - dotSize} ${y - dotSize} h ${size + 2 * dotSize} v ${size + 2 * dotSize} h ${-size - 2 * dotSize} z`),
346
463
  [ element, fill ];
347
464
  }
348
465
  }, qrCornerSquareFigures = {
@@ -774,6 +891,8 @@ class QRSVG {
774
891
  backgroundMask;
775
892
  dotsMask;
776
893
  lightDotsMask;
894
+ colorLayers=[];
895
+ segmentMask;
777
896
  qr;
778
897
  document;
779
898
  imageTools;
@@ -797,7 +916,7 @@ class QRSVG {
797
916
  width: 0,
798
917
  height: 0
799
918
  };
800
- if (this.qr = qr, this.options.image) {
919
+ if (this.qr = qr, this.segmentMask = this.getSegmentMask(count), this.options.image) {
801
920
  let size = await this.imageTools.getSize(this.options.image, this.options.imageOptions.crossOrigin), {imageOptions, errorCorrectionPercent} = this.options;
802
921
  if (imageOptions.mode == ImageMode.background) {
803
922
  let margin = this.options.backgroundOptions && this.options.backgroundOptions.margin || 0, maxSize = (this.options.size - 2 * margin * dotSize) * imageOptions.imageSize, {width, height} = size;
@@ -867,7 +986,33 @@ class QRSVG {
867
986
  height: drawImageSize.height,
868
987
  count,
869
988
  dotSize
870
- }), this.drawCorners();
989
+ }), this.drawCorners(), this.flattenColorLayers();
990
+ }
991
+ flattenColorLayers() {
992
+ let masks = new Map;
993
+ Array.from(this.defs.childNodes).forEach(node => {
994
+ let id = node.getAttribute?.("id");
995
+ id && masks.set(id, node);
996
+ });
997
+ for (let {rect, mask: id} of this.colorLayers) {
998
+ let mask = masks.get(id);
999
+ if (!mask) continue;
1000
+ let paths = mergeShapes(Array.from(mask.childNodes).filter(node => 1 == node.nodeType));
1001
+ if (!paths) continue;
1002
+ let elements = paths.map(({rule, d}) => {
1003
+ const path = this.document.createElementNS("http://www.w3.org/2000/svg", "path");
1004
+ path.setAttribute("d", d), "nonzero" != rule && path.setAttribute("fill-rule", rule);
1005
+ const fill = rect.getAttribute("fill");
1006
+ fill && path.setAttribute("fill", fill);
1007
+ const opacity = rect.getAttribute("opacity");
1008
+ return opacity && path.setAttribute("fill-opacity", opacity), path;
1009
+ }), layer = elements[0];
1010
+ if (elements.length > 1) {
1011
+ let group = this.document.createElementNS("http://www.w3.org/2000/svg", "g");
1012
+ elements.forEach(el => group.appendChild(el)), layer = group;
1013
+ }
1014
+ mask.parentNode.removeChild(mask), layer ? (layer.setAttribute("id", id), rect.parentNode.replaceChild(layer, rect)) : rect.parentNode.removeChild(rect);
1015
+ }
871
1016
  }
872
1017
  drawBackground() {
873
1018
  let options = this.options;
@@ -895,46 +1040,44 @@ class QRSVG {
895
1040
  if (!this.qr) throw new Error("QR code is not defined");
896
1041
  let options = this.options, count = this.qr.getModuleCount();
897
1042
  if (count > options.size) throw new Error("The canvas is too small");
898
- let dotSize = this.options.dotsOptions.size, size = options.size;
899
- options.imageOptions.mode == ImageMode.background && (size -= 2 * dotSize * (options.imageOptions.margin || 0));
900
- let beginning = Math.floor((options.size - count * dotSize) / 2), draw = getQrDotFigure(options.dotsOptions.type, options.plugins), orgDarw = draw;
1043
+ let dotSize = this.options.dotsOptions.size, beginning = Math.floor((options.size - count * dotSize) / 2), draw = getQrDotFigure(options.dotsOptions.type, options.plugins), orgDarw = draw;
901
1044
  this.dotsMask = this.createMask("mask-dot-color"), this.defs.appendChild(this.dotsMask),
902
1045
  options.imageOptions.mode == ImageMode.background && (this.lightDotsMask = this.createMask("mask-light-dot-color"),
903
1046
  this.defs.appendChild(this.lightDotsMask));
904
- let margin = 0, additionalDots = 0, fakeCount = count;
905
- options.shape === ShapeType.circle ? (margin = this.options.backgroundOptions && this.options.backgroundOptions.margin || 0,
906
- additionalDots = Math.floor((size / dotSize - count - 2 * margin) / 2), fakeCount = count + 2 * additionalDots) : options.imageOptions.mode == ImageMode.background && (additionalDots = 1,
907
- fakeCount = count + 2 * additionalDots);
908
- let fakeBeginning = beginning - additionalDots * dotSize, colorCount = count + 2 * additionalDots;
909
- this._fakeMatrix = new Array(fakeCount);
910
- let center = Math.floor(fakeCount / 2), rng = getRng(this._fakeMatrix, 0, 0, !1);
911
- for (let i = 0; i < fakeCount; i++) {
912
- this._fakeMatrix[i] = new Array(fakeCount);
913
- for (let j = 0; j < fakeCount; j++) {
914
- if (i > additionalDots - 1 && i < fakeCount - additionalDots && j > additionalDots - 1 && j < fakeCount - additionalDots) {
915
- let ii = i - additionalDots, jj = j - additionalDots;
1047
+ let mask = this.segmentMask || this.getSegmentMask(count), offsetX = mask.startX, offsetY = mask.startY, fakeCountX = mask.mask.length, fakeCountY = Math.max(0, ...mask.mask.map(col => col.length)), fakeBeginningX = beginning - offsetX * dotSize, fakeBeginningY = beginning - offsetY * dotSize, colorSize = Math.max(fakeCountX, fakeCountY) * dotSize, colorX = fakeBeginningX + (fakeCountX * dotSize - colorSize) / 2, colorY = fakeBeginningY + (fakeCountY * dotSize - colorSize) / 2;
1048
+ if (!mask.fitGradient) {
1049
+ let half = count / 2 + Math.max(offsetX, offsetY, fakeCountX - offsetX - count, fakeCountY - offsetY - count);
1050
+ colorX = colorY = beginning + (count / 2 - half) * dotSize, colorSize = 2 * half * dotSize;
1051
+ }
1052
+ this._fakeMatrix = new Array(fakeCountX);
1053
+ let rng = getRng(this._fakeMatrix, 0, 0, !1);
1054
+ for (let i = 0; i < fakeCountX; i++) {
1055
+ this._fakeMatrix[i] = new Array(fakeCountY);
1056
+ for (let j = 0; j < fakeCountY; j++) if (mask.mask[i][j]) {
1057
+ if (i > offsetX - 1 && i < offsetX + count && j > offsetY - 1 && j < offsetY + count) {
1058
+ let ii = i - offsetX, jj = j - offsetY;
916
1059
  filter && !filter(ii, jj) ? this._fakeMatrix[i][j] = void 0 : this._fakeMatrix[i][j] = !!this.qr.isDark(jj, ii);
917
1060
  continue;
918
1061
  }
919
- options.shape === ShapeType.circle && Math.sqrt((i - center) * (i - center) + (j - center) * (j - center)) > center ? this._fakeMatrix[i][j] = void 0 : this._fakeMatrix[i][j] = i != additionalDots - 1 && i != fakeCount - additionalDots && j != additionalDots - 1 && j != fakeCount - additionalDots ? this.qr.isDark(Math.floor(rng() * count), Math.floor(rng() * count)) : (j == additionalDots - 1 && (i < additionalDots + 8 || i > fakeCount - additionalDots - 9) || j == fakeCount - additionalDots && i < additionalDots + 8 || i == additionalDots - 1 && (j < additionalDots + 8 || j > fakeCount - additionalDots - 9) || i == fakeCount - additionalDots && j < additionalDots + 8) && void 0;
920
- }
1062
+ this._fakeMatrix[i][j] = !(i >= offsetX - 1 && i <= offsetX + count && j >= offsetY - 1 && j <= offsetY + count) && this.qr.isDark(Math.floor(rng() * count), Math.floor(rng() * count));
1063
+ } else this._fakeMatrix[i][j] = void 0;
921
1064
  }
922
- let typeNr = (count - 17) / 4, alignment = QRUtil.getPatternPosition(typeNr), alignmentMatrix = new Array(fakeCount);
923
- for (let i = 0; i < fakeCount; i++) {
924
- alignmentMatrix[i] = new Array(fakeCount);
925
- let iAlign = alignment.find(v => i - additionalDots > v - 3 && i - additionalDots < v + 3);
926
- for (let j = 0; j < fakeCount; j++) {
927
- let jAlign = alignment.find(v => j - additionalDots > v - 3 && j - additionalDots < v + 3);
1065
+ let typeNr = (count - 17) / 4, alignment = QRUtil.getPatternPosition(typeNr), alignmentMatrix = new Array(fakeCountX);
1066
+ for (let i = 0; i < fakeCountX; i++) {
1067
+ alignmentMatrix[i] = new Array(fakeCountY);
1068
+ let iAlign = alignment.find(v => i - offsetX > v - 3 && i - offsetX < v + 3);
1069
+ for (let j = 0; j < fakeCountY; j++) {
1070
+ let jAlign = alignment.find(v => j - offsetY > v - 3 && j - offsetY < v + 3);
928
1071
  alignmentMatrix[i][j] = void 0, null != this._fakeMatrix[i][j] && (this.lightDotsMask && iAlign && jAlign && (iAlign != alignment[0] && jAlign != alignment[0] || jAlign != alignment[0] && jAlign != alignment[alignment.length - 1] || iAlign != alignment[0] && iAlign != alignment[alignment.length - 1]) && (alignmentMatrix[i][j] = this._fakeMatrix[i][j]));
929
1072
  }
930
1073
  }
931
- for (let i = 0; i < fakeCount; i++) for (let j = 0; j < fakeCount; j++) {
1074
+ for (let i = 0; i < fakeCountX; i++) for (let j = 0; j < fakeCountY; j++) {
932
1075
  if (null == this._fakeMatrix[i][j]) continue;
933
1076
  this.lightDotsMask && (draw = null != alignmentMatrix[i][j] ? orgDarw : getQrDotFigure(DotType.tinySquare));
934
1077
  let alignmentNb = options.imageOptions.mode == ImageMode.background && null != alignmentMatrix[i][j];
935
1078
  this._fakeMatrix[i][j] ? this.dotsMask && this.dotsMask.appendChild(draw({
936
- x: fakeBeginning + i * dotSize,
937
- y: fakeBeginning + j * dotSize,
1079
+ x: fakeBeginningX + i * dotSize,
1080
+ y: fakeBeginningY + j * dotSize,
938
1081
  size: dotSize,
939
1082
  document: this.options.document,
940
1083
  getNeighbor: (xOffset, yOffset) => {
@@ -943,8 +1086,8 @@ class QRSVG {
943
1086
  },
944
1087
  getPRandom: getRng(this._fakeMatrix, i, j)
945
1088
  })) : this.lightDotsMask && this.lightDotsMask && this.lightDotsMask.appendChild(draw({
946
- x: fakeBeginning + i * dotSize,
947
- y: fakeBeginning + j * dotSize,
1089
+ x: fakeBeginningX + i * dotSize,
1090
+ y: fakeBeginningY + j * dotSize,
948
1091
  size: dotSize,
949
1092
  document: this.options.document,
950
1093
  getNeighbor: (xOffset, yOffset) => {
@@ -958,19 +1101,19 @@ class QRSVG {
958
1101
  options: options.imageOptions.fill.gradient,
959
1102
  color: options.imageOptions.fill.color,
960
1103
  additionalRotation: 0,
961
- x: fakeBeginning,
962
- y: fakeBeginning,
963
- height: colorCount * dotSize,
964
- width: colorCount * dotSize,
1104
+ x: colorX,
1105
+ y: colorY,
1106
+ height: colorSize,
1107
+ width: colorSize,
965
1108
  name: "light-dot-color"
966
1109
  }), this._dotsColor = this.createColor({
967
1110
  options: options.dotsOptions?.gradient,
968
1111
  color: options.dotsOptions.color,
969
1112
  additionalRotation: 0,
970
- x: fakeBeginning,
971
- y: fakeBeginning,
972
- height: colorCount * dotSize,
973
- width: colorCount * dotSize,
1113
+ x: colorX,
1114
+ y: colorY,
1115
+ height: colorSize,
1116
+ width: colorSize,
974
1117
  name: "dot-color"
975
1118
  });
976
1119
  }
@@ -1086,22 +1229,51 @@ class QRSVG {
1086
1229
  }
1087
1230
  });
1088
1231
  }
1232
+ getSegmentMask(count) {
1233
+ let options = this.options;
1234
+ if (options.mask) return options.mask;
1235
+ let dotSize = options.dotsOptions.size, offset = 0;
1236
+ if (options.shape === ShapeType.circle) {
1237
+ let size = options.size;
1238
+ options.imageOptions.mode == ImageMode.background && (size -= 2 * dotSize * (options.imageOptions.margin || 0));
1239
+ let margin = options.backgroundOptions && options.backgroundOptions.margin || 0;
1240
+ offset = Math.floor((size / dotSize - count - 2 * margin) / 2);
1241
+ } else options.imageOptions.mode == ImageMode.background && (offset = 1);
1242
+ let fakeCount = count + 2 * offset, center = Math.floor(fakeCount / 2);
1243
+ return {
1244
+ mask: Array.from({
1245
+ length: fakeCount
1246
+ }, (_col, i) => Array.from({
1247
+ length: fakeCount
1248
+ }, (_cell, j) => options.shape !== ShapeType.circle || i >= offset && i < offset + count && j >= offset && j < offset + count || Math.sqrt((i - center) * (i - center) + (j - center) * (j - center)) <= center)),
1249
+ startX: offset,
1250
+ startY: offset
1251
+ };
1252
+ }
1089
1253
  async drawImage({width, height, count, dotSize}) {
1090
1254
  let options = this.options, beginning = Math.floor((options.size - count * dotSize) / 2), margin = options.imageOptions.margin * dotSize;
1091
1255
  options.imageOptions.mode == ImageMode.background && (margin = 0);
1092
- let dx = beginning + margin + (count * dotSize - width) / 2, dy = beginning + margin + (count * dotSize - height) / 2, dw = width - 2 * margin, dh = height - 2 * margin, imageUrl = await this.imageTools.toDataURL(options.image || ""), image = this.document.createElementNS("http://www.w3.org/2000/svg", "image");
1093
- if (image.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", imageUrl || ""),
1094
- imageUrl.startsWith("data:image/svg+xml;")) {
1095
- let data = imageUrl.substring(19), i = data.indexOf(","), encoding = data.substring(0, i);
1096
- if (data = data.substring(i + 1), "base64" === encoding) data = "function" == typeof atob ? atob(data) : Buffer.from(data, "base64").toString("utf8"); else data = "";
1097
- if (data) {
1098
- let container = (new DOMParser).parseFromString(data, "text/xml"), rootEl = Array.from(container.getElementsByTagName("svg"));
1099
- rootEl.length && (image = rootEl[0], image.setAttribute("overflow", "visible"));
1256
+ let dx = beginning + margin + (count * dotSize - width) / 2, dy = beginning + margin + (count * dotSize - height) / 2, dw = width - 2 * margin, dh = height - 2 * margin, imageUrl = await this.imageTools.toDataURL(options.image || "");
1257
+ this._element.appendChild(function(document, imageUrl, {x, y, width, height}) {
1258
+ let image = document.createElementNS("http://www.w3.org/2000/svg", "image");
1259
+ if (image.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", imageUrl || ""),
1260
+ imageUrl.startsWith("data:image/svg+xml;")) {
1261
+ let data = imageUrl.substring(19), i = data.indexOf(","), encoding = data.substring(0, i);
1262
+ if (data = data.substring(i + 1), data = "base64" === encoding ? "function" == typeof atob ? atob(data) : Buffer.from(data, "base64").toString("utf8") : "",
1263
+ data) {
1264
+ let container = (new DOMParser).parseFromString(data, "text/xml"), rootEl = Array.from(container.getElementsByTagName("svg"));
1265
+ rootEl.length && (image = rootEl[0], image.setAttribute("overflow", "visible"));
1266
+ }
1100
1267
  }
1101
- }
1102
- image.setAttribute("x", numToAttr(dx)), image.setAttribute("y", numToAttr(dy)),
1103
- image.setAttribute("width", `${numToAttr(dw)}px`), image.setAttribute("height", `${numToAttr(dh)}px`),
1104
- this._element.appendChild(image);
1268
+ return image.setAttribute("x", numToAttr(x)), image.setAttribute("y", numToAttr(y)),
1269
+ image.setAttribute("width", svgAttr`${width}px`), image.setAttribute("height", svgAttr`${height}px`),
1270
+ image;
1271
+ }(this.document, imageUrl, {
1272
+ x: dx,
1273
+ y: dy,
1274
+ width: dw,
1275
+ height: dh
1276
+ }));
1105
1277
  }
1106
1278
  createColor(cfg) {
1107
1279
  let {width, height, x, y} = cfg;
@@ -1132,7 +1304,7 @@ class QRSVG {
1132
1304
  }
1133
1305
  return options.colorStops.forEach(stopCfg => {
1134
1306
  let stop = document.createElementNS("http://www.w3.org/2000/svg", "stop");
1135
- stop.setAttribute("offset", `${numToAttr(100 * stopCfg.offset)}%`);
1307
+ stop.setAttribute("offset", svgAttr`${100 * stopCfg.offset}%`);
1136
1308
  let parsed = parseColor(stopCfg.color);
1137
1309
  stop.setAttribute("stop-color", parsed.value), parsed.alpha < 1 && stop.setAttribute("stop-opacity", parsed.alpha.toFixed(7)),
1138
1310
  gradient.appendChild(stop);
@@ -1156,7 +1328,10 @@ class QRSVG {
1156
1328
  });
1157
1329
  return value && (rect.setAttribute("fill", value.value), value.gradient && this.defs.appendChild(value.gradient),
1158
1330
  value.opacity < 1 && rect.setAttribute("opacity", value.opacity.toFixed(7))), this._element.appendChild(rect),
1159
- value;
1331
+ this.colorLayers.push({
1332
+ rect,
1333
+ mask: `mask-${cfg.name}`
1334
+ }), value;
1160
1335
  }
1161
1336
  createMask(id) {
1162
1337
  let mask = this.document.createElementNS("http://www.w3.org/2000/svg", "clipPath");
@@ -1232,34 +1407,64 @@ let QRCodeStyling$1 = class {
1232
1407
  }
1233
1408
  setupSvg() {
1234
1409
  if (!this.qr) return;
1235
- let moduleCount = this.qr.getModuleCount();
1236
- this.options.imageOptions.mode == ImageMode.background && this.options.shape != ShapeType.circle && (moduleCount += 2);
1237
- let count = Math.ceil((this.options.shape == ShapeType.circle ? Math.sqrt(2) : 1) * moduleCount);
1238
- count % 2 == 0 && (count -= 1);
1239
- let margin = this.options.backgroundOptions && this.options.backgroundOptions.margin || 0;
1240
- this.options.imageOptions.mode == ImageMode.background && (margin += this.options.imageOptions.margin || 0);
1241
- let size = Math.ceil(this.options.dotsOptions.size * (count + 2 * margin));
1410
+ let mask, size, appliedMask, moduleCount = this.qr.getModuleCount(), isBackground = this.options.imageOptions.mode == ImageMode.background, padding = isBackground ? 1 : 0;
1411
+ for (let plugin of this.options.plugins || []) if (mask = plugin.applyMask?.(moduleCount, padding) || void 0,
1412
+ mask) break;
1413
+ if (mask) {
1414
+ let {startX, startY} = mask, columns = mask.mask.slice(startX, startX + moduleCount);
1415
+ if (startX < padding || startY < padding || startX + moduleCount + padding > mask.mask.length || columns.length < moduleCount || columns.some(col => startY + moduleCount + padding > col.length)) throw new Error(padding ? `The mask must extend ${padding} segment(s) beyond the QR code on each side` : "The QR code must lie inside the mask");
1416
+ if ([ [ 0, 0 ], [ moduleCount - 7, 0 ], [ 0, moduleCount - 7 ] ].some(([x, y]) => columns.slice(x, x + 7).some(col => col.slice(startY + y, startY + y + 7).some(v => !v)))) throw new Error("The QR code corners must not be hidden by the mask");
1417
+ }
1418
+ if (mask) {
1419
+ let dotSize = this.options.dotsOptions.size, margin = isBackground ? 1 + (this.options.imageOptions.margin || 0) : 0;
1420
+ size = Math.ceil(dotSize * (moduleCount + 2 * margin));
1421
+ let beginning = Math.floor((size - moduleCount * dotSize) / 2);
1422
+ appliedMask = {
1423
+ ...mask,
1424
+ count: moduleCount,
1425
+ dotSize,
1426
+ x: beginning - mask.startX * dotSize,
1427
+ y: beginning - mask.startY * dotSize
1428
+ };
1429
+ } else {
1430
+ this.options.imageOptions.mode == ImageMode.background && this.options.shape != ShapeType.circle && (moduleCount += 2);
1431
+ let count = Math.ceil((this.options.shape == ShapeType.circle ? Math.sqrt(2) : 1) * moduleCount);
1432
+ count % 2 == 0 && (count -= 1);
1433
+ let margin = this.options.backgroundOptions && this.options.backgroundOptions.margin || 0;
1434
+ this.options.imageOptions.mode == ImageMode.background && (margin += this.options.imageOptions.margin || 0),
1435
+ size = Math.ceil(this.options.dotsOptions.size * (count + 2 * margin));
1436
+ }
1242
1437
  this.qrSVG = new QRSVG({
1243
1438
  ...this.options,
1439
+ ...mask && {
1440
+ shape: ShapeType.square,
1441
+ backgroundOptions: void 0
1442
+ },
1244
1443
  width: size,
1245
1444
  height: size,
1246
1445
  size,
1446
+ mask,
1247
1447
  errorCorrectionPercent: ErrorCorrectionPercents[this.options.qrOptions.errorCorrectionLevel]
1248
- }), this.svgDrawingPromise = this.qrSVG.drawQR(this.qr).then(() => {
1249
- this.qrSVG?.element && (this.options.plugins?.forEach(plugin => plugin.postProcess?.(this.qrSVG.element, {
1250
- ...this.options,
1251
- width: size,
1252
- height: size,
1253
- size
1254
- }, {
1255
- dots: this.qrSVG?.dotsColor,
1256
- background: this.qrSVG?.backgroundColor
1257
- })), this.extension?.(this.qrSVG.element, {
1258
- ...this.options,
1259
- width: size,
1260
- height: size,
1261
- size
1262
- }));
1448
+ }), this.svgDrawingPromise = this.qrSVG.drawQR(this.qr).then(async () => {
1449
+ if (this.qrSVG?.element) {
1450
+ if (this.options.plugins) for (let plugin of this.options.plugins) await (plugin.postProcess?.(this.qrSVG.element, {
1451
+ ...this.options,
1452
+ width: size,
1453
+ height: size,
1454
+ size,
1455
+ mask: appliedMask
1456
+ }, {
1457
+ dots: this.qrSVG?.dotsColor,
1458
+ background: this.qrSVG?.backgroundColor
1459
+ }));
1460
+ this.extension?.(this.qrSVG.element, {
1461
+ ...this.options,
1462
+ width: size,
1463
+ height: size,
1464
+ size,
1465
+ mask: appliedMask
1466
+ });
1467
+ }
1263
1468
  });
1264
1469
  }
1265
1470
  };
@@ -1299,8 +1504,8 @@ class QRCodeStyling extends QRCodeStyling$1 {
1299
1504
  imageTools: options.imageTools || {
1300
1505
  toDataURL: url => "string" == typeof url && url.startsWith("data:") ? Promise.resolve(url) : loadImage(url).then(({contentType, data}) => `data:${contentType?.replace("application/xml", "image/svg+xml")};base64,${data.toString("base64")}`),
1301
1506
  getSize: async src => {
1302
- let {data} = Buffer.isBuffer(options.image) ? {
1303
- data: options.image
1507
+ let {data} = Buffer.isBuffer(src) ? {
1508
+ data: src
1304
1509
  } : await loadImage(src), meta = await sharp(data).metadata();
1305
1510
  return {
1306
1511
  width: meta.width,