1export function roundRect(ctx, x, y, w, h, r) { 2 if (w < 2 * r) r = w / 2; 3 if (h < 2 * r) r = h / 2; 4 ctx.beginPath(); 5 ctx.moveTo(x + r, y); 6 ctx.lineTo(x + w - r, y); 7 ctx.quadraticCurveTo(x + w, y, x + w, y + r); 8 ctx.lineTo(x + w, y + h - r); 9 ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); 10 ctx.lineTo(x + r, y + h); 11 ctx.quadraticCurveTo(x, y + h, x, y + h - r); 12 ctx.lineTo(x, y + r); 13 ctx.quadraticCurveTo(x, y, x + r, y); 14 ctx.closePath(); 15} 16 17export function leafRect(ctx, x, y, w, h, r) { 18 ctx.beginPath(); 19 ctx.moveTo(x + r, y); 20 ctx.lineTo(x + w, y); 21 ctx.lineTo(x + w, y + h - r); 22 ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); 23 ctx.lineTo(x, y + h); 24 ctx.lineTo(x, y + r); 25 ctx.quadraticCurveTo(x, y, x + r, y); 26 ctx.closePath(); 27} 28 29export function drawModule(ctx, x, y, size, style, extra) { 30 const s = size, 31 h = s / 2, 32 r = s * 0.35; 33 switch (style) { 34 case 'rounded': 35 const rad = s * 0.3; 36 ctx.beginPath(); 37 ctx.moveTo(x + rad, y); 38 ctx.lineTo(x + s - rad, y); 39 ctx.quadraticCurveTo(x + s, y, x + s, y + rad); 40 ctx.lineTo(x + s, y + s - rad); 41 ctx.quadraticCurveTo(x + s, y + s, x + s - rad, y + s); 42 ctx.lineTo(x + rad, y + s); 43 ctx.quadraticCurveTo(x, y + s, x, y + s - rad); 44 ctx.lineTo(x, y + rad); 45 ctx.quadraticCurveTo(x, y, x + rad, y); 46 ctx.closePath(); 47 ctx.fill(); 48 break; 49 case 'dots': 50 ctx.beginPath(); 51 ctx.arc(x + h, y + h, r, 0, Math.PI * 2); 52 ctx.fill(); 53 break; 54 case 'diamond': 55 ctx.beginPath(); 56 ctx.moveTo(x + h, y); 57 ctx.lineTo(x + s, y + h); 58 ctx.lineTo(x + h, y + s); 59 ctx.lineTo(x, y + h); 60 ctx.closePath(); 61 ctx.fill(); 62 break; 63 case 'bars': 64 ctx.fillRect(x + s * 0.15, y, s * 0.7, s); 65 break; 66 case 'connected': 67 const g = s * 0.08, 68 cr = s * 0.35, 69 n = extra?.neighbors || {}, 70 x1 = n.left ? x - g : x + g, 71 x2 = n.right ? x + s + g : x + s - g, 72 y1 = n.top ? y - g : y + g, 73 y2 = n.bottom ? y + s + g : y + s - g, 74 rr = cr; 75 ctx.beginPath(); 76 ctx.moveTo(x1 + (n.left ? 0 : rr), y1); 77 ctx.lineTo(x2 - (n.right ? 0 : rr), y1); 78 if (!n.right) ctx.quadraticCurveTo(x2, y1, x2, y1 + rr); 79 else ctx.lineTo(x2, y1); 80 ctx.lineTo(x2, y2 - (n.bottom ? 0 : rr)); 81 if (!n.bottom) ctx.quadraticCurveTo(x2, y2, x2 - rr, y2); 82 else ctx.lineTo(x2, y2); 83 ctx.lineTo(x1 + (n.left ? 0 : rr), y2); 84 if (!n.left) ctx.quadraticCurveTo(x1, y2, x1, y2 - rr); 85 else ctx.lineTo(x1, y2); 86 ctx.lineTo(x1, y1 + (n.top ? 0 : rr)); 87 if (!n.top) ctx.quadraticCurveTo(x1, y1, x1 + rr, y1); 88 else ctx.lineTo(x1, y1); 89 ctx.closePath(); 90 ctx.fill(); 91 break; 92 default: 93 ctx.fillRect(x, y, s, s); 94 } 95} 96 97export function drawFinderPattern(ctx, x, y, cellSize, fg, bg, style) { 98 const s7 = cellSize * 7, 99 s5 = cellSize * 5, 100 s3 = cellSize * 3, 101 r7 = cellSize * 1.5, 102 r5 = cellSize * 1.0, 103 r3 = cellSize * 0.8; 104 switch (style) { 105 case 'rounded': 106 ctx.fillStyle = fg; 107 roundRect(ctx, x, y, s7, s7, r7); 108 ctx.fill(); 109 ctx.fillStyle = bg; 110 roundRect(ctx, x + cellSize, y + cellSize, s5, s5, r5); 111 ctx.fill(); 112 ctx.fillStyle = fg; 113 roundRect(ctx, x + cellSize * 2, y + cellSize * 2, s3, s3, r3); 114 ctx.fill(); 115 break; 116 case 'circle': 117 const cx = x + s7 / 2, 118 cy = y + s7 / 2; 119 ctx.fillStyle = fg; 120 ctx.beginPath(); 121 ctx.arc(cx, cy, s7 / 2, 0, Math.PI * 2); 122 ctx.fill(); 123 ctx.fillStyle = bg; 124 ctx.beginPath(); 125 ctx.arc(cx, cy, s5 / 2, 0, Math.PI * 2); 126 ctx.fill(); 127 ctx.fillStyle = fg; 128 ctx.beginPath(); 129 ctx.arc(cx, cy, s3 / 2, 0, Math.PI * 2); 130 ctx.fill(); 131 break; 132 case 'leaf': 133 ctx.fillStyle = fg; 134 leafRect(ctx, x, y, s7, s7, r7); 135 ctx.fill(); 136 ctx.fillStyle = bg; 137 leafRect(ctx, x + cellSize, y + cellSize, s5, s5, r5); 138 ctx.fill(); 139 ctx.fillStyle = fg; 140 leafRect(ctx, x + cellSize * 2, y + cellSize * 2, s3, s3, r3); 141 ctx.fill(); 142 break; 143 default: 144 ctx.fillStyle = fg; 145 ctx.fillRect(x, y, s7, s7); 146 ctx.fillStyle = bg; 147 ctx.fillRect(x + cellSize, y + cellSize, s5, s5); 148 ctx.fillStyle = fg; 149 ctx.fillRect(x + cellSize * 2, y + cellSize * 2, s3, s3); 150 } 151} 152 153export function drawFramePreset( 154 ctx,
155 frameStyle, 156 fg, 157 cellSize, 158 mc, 159 margin, 160 offX, 161 offY, 162 size, 163 labels = {}, 164) { 165 if (!frameStyle || frameStyle === 'none') return; 166 const qrStartX = offX + margin * cellSize, 167 qrStartY = offY + margin * cellSize, 168 qrSize = mc * cellSize, 169 qrEndX = qrStartX + qrSize, 170 qrEndY = qrStartY + qrSize, 171 qrCenter = size / 2; 172 173 const gap = Math.max(8, Math.floor(size * 0.06)); 174 const fX = qrStartX - gap, 175 fY = qrStartY - gap, 176 fW = qrSize + gap * 2, 177 fH = qrSize + gap * 2; 178 179 ctx.lineJoin = 'round'; 180 ctx.lineCap = 'round'; 181 182 if (frameStyle === 'classic') { 183 ctx.strokeStyle = fg; 184 ctx.lineWidth = Math.max(2, Math.floor(size * 0.015)); 185 ctx.strokeRect(fX, fY, fW, fH); 186 } else if (frameStyle === 'scan') { 187 ctx.strokeStyle = fg; 188 ctx.lineWidth = Math.max(2, Math.floor(size * 0.015)); 189 ctx.strokeRect(fX, fY, fW, fH); 190 const fontSize = Math.max(Math.floor(size * 0.08), 10); 191 ctx.font = `800 ${fontSize}px -apple-system, sans-serif`; 192 ctx.fillStyle = fg; 193 ctx.textAlign = 'center'; 194 ctx.fillText(labels.scanMe || '', qrCenter, qrEndY + gap + fontSize); 195 } else if (frameStyle === 'badge') { 196 const badgeW = Math.floor(size * 0.7), 197 badgeH = Math.floor(size * 0.16), 198 badgeX = (size - badgeW) / 2, 199 badgeY = fY - badgeH - Math.floor(size * 0.01); 200 ctx.fillStyle = fg; 201 roundRect(ctx, badgeX, badgeY, badgeW, badgeH, Math.floor(size * 0.04)); 202 ctx.fill(); 203 ctx.fillStyle = '#ffffff'; 204 ctx.textAlign = 'center'; 205 const fontSize = Math.max(10, Math.floor(badgeH * 0.5)); 206 ctx.font = `800 ${fontSize}px -apple-system, sans-serif`; 207 ctx.fillText(labels.scanHere || '', qrCenter, badgeY + badgeH * 0.65); 208 ctx.strokeStyle = fg; 209 ctx.lineWidth = Math.max(2, Math.floor(size * 0.015)); 210 ctx.strokeRect(fX, fY, fW, fH); 211 } else if (frameStyle === 'phone') { 212 const pW = fW + Math.floor(size * 0.06), 213 pH = fH + Math.floor(size * 0.2), 214 pX = (size - pW) / 2, 215 pY = (size - pH) / 2 + Math.floor(size * 0.02); 216 ctx.strokeStyle = fg; 217 ctx.lineWidth = Math.max(4, Math.floor(size * 0.035)); 218 roundRect(ctx, pX, pY, pW, pH, Math.floor(size * 0.1)); 219 ctx.stroke(); 220 ctx.fillStyle = fg; 221 const nW = Math.floor(pW * 0.35), 222 nH = Math.floor(pH * 0.05); 223 roundRect(ctx, qrCenter - nW / 2, pY - 2, nW, nH, nH / 2); 224 ctx.fill(); 225 const hW = Math.floor(pW * 0.25), 226 hH = Math.max(2, Math.floor(pH * 0.015)); 227 roundRect(ctx, qrCenter - hW / 2, pY + pH - hH * 2.5, hW, hH, hH / 2); 228 ctx.fill(); 229 } else if (frameStyle === 'chat') { 230 ctx.strokeStyle = fg; 231 ctx.lineWidth = Math.max(3, Math.floor(size * 0.02)); 232 roundRect(ctx, fX, fY, fW, fH, Math.floor(size * 0.08)); 233 ctx.stroke(); 234 const tW = Math.floor(fW * 0.15); 235 ctx.beginPath(); 236 ctx.moveTo(qrCenter - tW, fY + fH); 237 ctx.lineTo(qrCenter, fY + fH + tW); 238 ctx.lineTo(qrCenter + tW, fY + fH); 239 ctx.fillStyle = fg; 240 ctx.fill(); 241 } else if (frameStyle === 'bracket') { 242 const len = Math.floor(fW * 0.25); 243 ctx.strokeStyle = fg; 244 ctx.lineWidth = Math.max(4, Math.floor(size * 0.025)); 245 ctx.beginPath(); 246 ctx.moveTo(fX, fY + len); 247 ctx.lineTo(fX, fY); 248 ctx.lineTo(fX + len, fY); 249 ctx.stroke(); 250 ctx.beginPath(); 251 ctx.moveTo(fX + fW - len, fY); 252 ctx.lineTo(fX + fW, fY); 253 ctx.lineTo(fX + fW, fY + len); 254 ctx.stroke(); 255 ctx.beginPath(); 256 ctx.moveTo(fX, fY + fH - len); 257 ctx.lineTo(fX, fY + fH); 258 ctx.lineTo(fX + len, fY + fH); 259 ctx.stroke(); 260 ctx.beginPath(); 261 ctx.moveTo(fX + fW - len, fY + fH); 262 ctx.lineTo(fX + fW, fY + fH); 263 ctx.lineTo(fX + fW, fY + fH - len); 264 ctx.stroke(); 265 } 266} 267 268export function drawLogo(ctx, x, y, size, logoImage) { 269 if (!logoImage) return; 270 const logoSize = Math.floor(size * 0.22), 271 lx = x + Math.floor((size - logoSize) / 2), 272 ly = y + Math.floor((size - logoSize) / 2), 273 pad = 3; 274 ctx.fillStyle = '#ffffff'; 275 ctx.fillRect(lx - pad, ly - pad, logoSize + pad * 2, logoSize + pad * 2); 276 ctx.drawImage(logoImage, lx, ly, logoSize, logoSize); 277} 278 279export function drawCaption(ctx, text, size, fg) { 280 if (!text) return 0; 281 const fontSize = Math.max(Math.floor(size * 0.08), 10); 282 ctx.font = `600 ${fontSize}px -apple-system, BlinkMacSystemFont, sans-serif`; 283 ctx.fillStyle = fg;
284 ctx.textAlign = 'center'; 285 ctx.fillText(text, size / 2, size + fontSize + 4); 286 return fontSize + 8; 287} 288 289export function lerpColor(c1, c2, t) { 290 const r1 = parseInt(c1.slice(1, 3), 16), 291 g1 = parseInt(c1.slice(3, 5), 16), 292 b1 = parseInt(c1.slice(5, 7), 16), 293 r2 = parseInt(c2.slice(1, 3), 16), 294 g2 = parseInt(c2.slice(3, 5), 16), 295 b2 = parseInt(c2.slice(5, 7), 16), 296 r = Math.round(r1 + (r2 - r1) * t), 297 g = Math.round(g1 + (g2 - g1) * t), 298 b = Math.round(b1 + (b2 - b1) * t); 299 return `rgb(${r},${g},${b})`; 300} 301 302export function shiftHue(hex) { 303 let r = parseInt(hex.slice(1, 3), 16) / 255, 304 g = parseInt(hex.slice(3, 5), 16) / 255, 305 b = parseInt(hex.slice(5, 7), 16) / 255; 306 const max = Math.max(r, g, b), 307 min = Math.min(r, g, b), 308 d = max - min; 309 let h = 0, 310 s = max === 0 ? 0 : d / max, 311 v = max; 312 if (d !== 0) { 313 if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6; 314 else if (max === g) h = ((b - r) / d + 2) / 6; 315 else h = ((r - g) / d + 4) / 6; 316 } 317 h = (h + 0.33) % 1; 318 const i = Math.floor(h * 6), 319 f = h * 6 - i, 320 p = v * (1 - s), 321 q = v * (1 - f * s), 322 t2 = v * (1 - (1 - f) * s); 323 let r2, g2, b2; 324 switch (i % 6) { 325 case 0: 326 r2 = v; 327 g2 = t2; 328 b2 = p; 329 break; 330 case 1: 331 r2 = q; 332 g2 = v; 333 b2 = p; 334 break; 335 case 2: 336 r2 = p; 337 g2 = v; 338 b2 = t2; 339 break; 340 case 3: 341 r2 = p; 342 g2 = q; 343 b2 = v; 344 break; 345 case 4: 346 r2 = t2; 347 g2 = p; 348 b2 = v; 349 break; 350 case 5: 351 r2 = v; 352 g2 = p; 353 b2 = q; 354 break; 355 } 356 return ( 357 '#' + 358 [r2, g2, b2] 359 .map((x) => 360 Math.round(x * 255) 361 .toString(16) 362 .padStart(2, '0'), 363 ) 364 .join('') 365 ); 366} 367 368export function isStrictScanPayload(text) { 369 const t = (text || '').trim(); 370 return t.includes('BEGIN:VCARD') || t.startsWith('WIFI:'); 371} 372 373export function isFinderCell(r, c, mc) { 374 if (r < 7 && c < 7) return true; 375 if (r < 7 && c >= mc - 7) return true; 376 if (r >= mc - 7 && c < 7) return true; 377 return false; 378} 379 380export function getNeighbors(qr, row, col) { 381 const mc = qr.getModuleCount(); 382 return { 383 top: row > 0 && qr.isDark(row - 1, col), 384 bottom: row < mc - 1 && qr.isDark(row + 1, col), 385 left: col > 0 && qr.isDark(row, col - 1), 386 right: col < mc - 1 && qr.isDark(row, col + 1), 387 }; 388} 389 390export function buildSVG( 391 qr, 392 { caption, fg, bg, targetSize, logoData = null, visualBlending = false }, 393) { 394 const mc = qr.getModuleCount(), 395 margin = 2; 396 397 const { cellSize, offsetX, offsetY, qrPx } = calculateQRLayout(targetSize, mc, margin); 398 const esc = (s) => 399 (s || '').replace( 400 /[&<>"']/g, 401 (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c], 402 ); 403 404 const isBlending = visualBlending && logoData; 405 const captionH = caption ? Math.max(Math.floor(targetSize * 0.08), 14) + 12 : 0; 406 const totalH = targetSize + captionH; 407 408 let svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${targetSize}" height="${totalH}" viewBox="0 0 ${targetSize} ${totalH}">`; 409 410 svg += `<rect width="${targetSize}" height="${totalH}" fill="${esc(bg)}" rx="16"/>`; 411 412 if (visualBlending && logoData) { 413 svg += `<image x="${offsetX}" y="${offsetY}" width="${qrPx}" height="${qrPx}" xlink:href="${esc(logoData)}" opacity="0.6" preserveAspectRatio="xMidYMid slice" clip-path="url(#qr-clip)"/>`; 414 svg += `<defs><clipPath id="qr-clip"><rect x="${offsetX}" y="${offsetY}" width="${qrPx}" height="${qrPx}" rx="16"/></clipPath></defs>`; 415 416 const overlayColor = bg.toLowerCase() === '#ffffff' ? 'white' : 'black'; 417 svg += `<rect x="${offsetX}" y="${offsetY}" width="${qrPx}" height="${qrPx}" fill="${overlayColor}" opacity="0.05" rx="16"/>`; 418 } 419 420 for (let r = 0; r < mc; r++) { 421 for (let c = 0; c < mc; c++) { 422 if (isFinderCell(r, c, mc)) continue; 423 if (qr.isDark(r, c)) { 424 const mx = offsetX + (c + margin) * cellSize; 425 const my = offsetY + (r + margin) * cellSize; 426 svg += `<rect x="${mx}" y="${my}" width="${cellSize}" height="${cellSize}" fill="${esc(fg)}"/>`; 427 } 428 } 429 } 430 431 const s7 = cellSize * 7; 432 const finders = [ 433 { x: offsetX + margin * cellSize, y: offsetY + margin * cellSize }, 434 { x: offsetX + (mc - 7 + margin) * cellSize, y: offsetY + margin * cellSize }, 435 { x: offsetX + margin * cellSize, y: offsetY + (mc - 7 + margin) * cellSize }, 436 ]; 437
438 finders.forEach((f) => { 439 if (isBlending) { 440 svg += `<rect x="${f.x - cellSize}" y="${f.y - cellSize}" width="${s7 + cellSize * 2}" height="${s7 + cellSize * 2}" fill="${esc(bg)}" opacity="0.8"/>`; 441 } 442 443 svg += `<rect x="${f.x}" y="${f.y}" width="${s7}" height="${s7}" fill="${esc(fg)}"/>`; 444 svg += `<rect x="${f.x + cellSize}" y="${f.y + cellSize}" width="${cellSize * 5}" height="${cellSize * 5}" fill="${esc(bg)}"/>`; 445 svg += `<rect x="${f.x + cellSize * 2}" y="${f.y + cellSize * 2}" width="${cellSize * 3}" height="${cellSize * 3}" fill="${esc(fg)}"/>`; 446 }); 447 448 if (!isBlending && logoData) { 449 const logoSize = Math.floor(qrPx * 0.22); 450 const lx = offsetX + Math.floor((qrPx - logoSize) / 2); 451 const ly = offsetY + Math.floor((qrPx - logoSize) / 2); 452 453 svg += `<rect x="${lx - 3}" y="${ly - 3}" width="${logoSize + 6}" height="${logoSize + 6}" fill="white"/>`; 454 svg += `<image x="${lx}" y="${ly}" width="${logoSize}" height="${logoSize}" xlink:href="${esc(logoData)}"/>`; 455 } 456 457 if (caption) { 458 const fontSize = Math.max(Math.floor(targetSize * 0.08), 14); 459 svg += `<text x="${targetSize / 2}" y="${targetSize + fontSize}" text-anchor="middle" font-family="-apple-system, sans-serif" font-weight="600" font-size="${fontSize}" fill="${esc(fg)}">${esc(caption)}</text>`; 460 } 461 462 svg += `</svg>`; 463 return svg; 464} 465
466export function renderSmallQR( 467 qrcodeInstance, 468 canvas, 469 { 470 content, 471 fg = '#1a1a1a', 472 bg = '#ffffff', 473 style = 'square', 474 fStyle = 'square', 475 frameStyle = 'none', 476 frameColor = null, 477 size = 44, 478 margin = 1, 479 labels = {}, 480 } = {}, 481) { 482 const qr = qrcodeInstance(0, 'M'); 483 qr.addData(content || 'miqr.mx', 'Byte'); 484 qr.make(); 485 486 canvas.width = size; 487 canvas.height = size; 488 const ctx = canvas.getContext('2d'); 489 ctx.fillStyle = bg; 490 ctx.fillRect(0, 0, size, size); 491 492 const mc = qr.getModuleCount(), 493 cs = Math.floor(size / (mc + margin * 2)), 494 off = Math.floor((size - (mc + margin * 2) * cs) / 2); 495 496 for (let r = 0; r < mc; r++) { 497 for (let c = 0; c < mc; c++) { 498 if (isFinderCell(r, c, mc)) continue; 499 if (!qr.isDark(r, c)) continue; 500 ctx.fillStyle = fg; 501 const extra = style === 'connected' ? { neighbors: getNeighbors(qr, r, c) } : null; 502 drawModule(ctx, off + (c + margin) * cs, off + (r + margin) * cs, cs, style, extra); 503 } 504 } 505 506 drawFinderPattern(ctx, off + margin * cs, off + margin * cs, cs, fg, bg, fStyle); 507 drawFinderPattern(ctx, off + (mc - 7 + margin) * cs, off + margin * cs, cs, fg, bg, fStyle); 508 drawFinderPattern(ctx, off + margin * cs, off + (mc - 7 + margin) * cs, cs, fg, bg, fStyle); 509 510 if (frameStyle !== 'none') { 511 drawFramePreset(ctx, frameStyle, frameColor || fg, cs, mc, margin, off, off, size, labels); 512 } 513} 514 515export function calculateQRLayout(targetSize, mc, margin) { 516 const topPad = Math.floor(targetSize * 0.2); 517 const bottomPad = Math.floor(targetSize * 0.25); 518 const sidePad = Math.floor(targetSize * 0.08); 519 const availableW = targetSize - sidePad * 2; 520 const availableH = targetSize - (topPad + bottomPad); 521 const availableSize = Math.min(availableW, availableH); 522 const cellSize = Math.floor(availableSize / (mc + margin * 2)); 523 const qrPx = (mc + margin * 2) * cellSize; 524 const offsetX = Math.floor((targetSize - qrPx) / 2); 525 const offsetY = Math.floor((targetSize - (topPad + bottomPad) - qrPx) / 2) + topPad; 526 return { cellSize, offsetX, offsetY, qrPx, topPad, bottomPad, sidePad }; 527} 528 529export function drawBackground(ctx, x, y, size, backgroundImage, alpha = 0.5) { 530 if (!backgroundImage) return; 531 ctx.save(); 532 ctx.globalAlpha = alpha; 533 534 roundRect(ctx, x, y, size, size, 16); 535 ctx.clip(); 536 const aspect = backgroundImage.width / backgroundImage.height; 537 let drawW, drawH, drawX, drawY; 538 if (aspect > 1) { 539 drawH = size; 540 drawW = size * aspect; 541 drawX = x - (drawW - size) / 2; 542 drawY = y; 543 } else { 544 drawW = size; 545 drawH = size / aspect; 546 drawX = x; 547 drawY = y - (drawH - size) / 2; 548 } 549 ctx.drawImage(backgroundImage, drawX, drawY, drawW, drawH); 550 ctx.restore(); 551} 552 553export function drawQRComponents( 554 ctx, 555 qr, 556 { 557 fg, 558 bg, 559 style, 560 fStyle, 561 frameStyle, 562 frameColor, 563 cellSize, 564 mc, 565 margin, 566 offsetX, 567 offsetY, 568 targetSize, 569 labels, 570 backgroundImage = null, 571 visualBlending = false, 572 }, 573) { 574 const qrPx = (mc + margin * 2) * cellSize; 575 const isBlending = visualBlending && backgroundImage; 576 577 if (isBlending) { 578 drawBackground(ctx, offsetX, offsetY, qrPx, backgroundImage, 0.6); 579 ctx.fillStyle = bg.toLowerCase() === '#ffffff' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'; 580 ctx.fillRect(offsetX, offsetY, qrPx, qrPx); 581 } 582 583 if (frameStyle !== 'none') { 584 drawFramePreset( 585 ctx, 586 frameStyle, 587 frameColor || fg, 588 cellSize, 589 mc, 590 margin, 591 offsetX, 592 offsetY, 593 targetSize, 594 labels, 595 ); 596 } 597 598 ctx.save(); 599 if (isBlending) { 600 ctx.globalAlpha = 1.0; 601 } 602 for (let r = 0; r < mc; r++) { 603 for (let c = 0; c < mc; c++) { 604 if (isFinderCell(r, c, mc)) continue; 605 if (!qr.isDark(r, c)) continue; 606 ctx.fillStyle = fg; 607 const extra = style === 'connected' ? { neighbors: getNeighbors(qr, r, c) } : null; 608 drawModule( 609 ctx, 610 offsetX + (c + margin) * cellSize, 611 offsetY + (r + margin) * cellSize, 612 cellSize, 613 style, 614 extra, 615 ); 616 } 617 }
618 ctx.restore(); 619 620 ctx.save(); 621 if (isBlending) { 622 const s7 = cellSize * 7; 623 const finders = [ 624 { x: offsetX + margin * cellSize, y: offsetY + margin * cellSize }, 625 { x: offsetX + (mc - 7 + margin) * cellSize, y: offsetY + margin * cellSize }, 626 { x: offsetX + margin * cellSize, y: offsetY + (mc - 7 + margin) * cellSize }, 627 ]; 628 finders.forEach((f) => { 629 ctx.fillStyle = bg; 630 ctx.globalAlpha = 0.8; 631 ctx.fillRect(f.x - cellSize, f.y - cellSize, s7 + cellSize * 2, s7 + cellSize * 2); 632 }); 633 ctx.globalAlpha = 1.0; 634 } 635 drawFinderPattern( 636 ctx, 637 offsetX + margin * cellSize, 638 offsetY + margin * cellSize, 639 cellSize, 640 fg, 641 bg, 642 fStyle, 643 ); 644 drawFinderPattern( 645 ctx, 646 offsetX + (mc - 7 + margin) * cellSize, 647 offsetY + margin * cellSize, 648 cellSize, 649 fg, 650 bg, 651 fStyle, 652 ); 653 drawFinderPattern( 654 ctx, 655 offsetX + margin * cellSize, 656 offsetY + (mc - 7 + margin) * cellSize, 657 cellSize, 658 fg, 659 bg, 660 fStyle, 661 ); 662 ctx.restore(); 663}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.