1/** 2 * SBQR Custom QR Code Renderer v4.0 3 * Responsive HiDPI â modules: square/dot/rounded/diamond 4 * Gradient, coins stylisés, logo centré, inversion, coins QR arrondis. 5 */ 6(function (win) { 7 'use strict'; 8 9 function SBQRRenderer(opts) { 10 this.opts = Object.assign({ 11 text: 'https://senat.bi/', 12 maxSize: 300, 13 margin: 2, 14 correctLevel: 'H', 15 moduleStyle: 'square', 16 moduleGap: 8, 17 colorDark: '#000000', 18 colorLight: '#ffffff', 19 transparentBg: false, 20 invert: false, 21 gradientEnabled: false, 22 gradientColor2: '#006633', 23 gradientDir: 'diagonal', 24 eyeOuterStyle: 'rounded', 25 eyeInnerStyle: 'square', 26 eyeColorEnabled: false, 27 eyeColor: '#000000', 28 logoInQR: false, 29 logoUrl: '', 30 logoSizeRatio: 0.22, 31 logoBgColor: '#ffffff', 32 logoBgRadius: 12, 33 logoPadding: 6, 34 logoBgTransparent: false, 35 shadowEnabled: false, 36 shadowColor: 'rgba(0,0,0,0.25)', 37 qrCornerRadius: 0, 38 }, opts); 39 this._matrix = null; 40 this._count = 0; 41 this._canvas = null; 42 this._container = null; 43 this._ro = null; 44 this._logoImg = null; 45 this._currentSize = 0; 46 } 47 48 /* âââ Public ââââââââââââââââââââââââââââââââââ */ 49 SBQRRenderer.prototype.render = function (container) { 50 this._container = container; 51 this._buildMatrix(); 52 this._paint(); 53 this._watchResize(); 54 }; 55 56 SBQRRenderer.prototype.destroy = function () { 57 if (this._ro) { this._ro.disconnect(); this._ro = null; } 58 }; 59 60 /* âââ Matrix ââââââââââââââââââââââââââââââââââ */ 61 SBQRRenderer.prototype._buildMatrix = function () { 62 var opts = this.opts; 63 var tmp = document.createElement('div'); 64 tmp.style.cssText = 'position:absolute;top:-9999px;left:-9999px;width:1px;height:1px;overflow:hidden;'; 65 document.body.appendChild(tmp); 66 try { 67 var qr = new QRCode(tmp, { 68 text: opts.text, width: 10, height: 10, 69 correctLevel: QRCode.CorrectLevel[opts.correctLevel] || QRCode.CorrectLevel.H 70 }); 71 var d = qr._oQRCode, n = d.getModuleCount(), mat = []; 72 for (var r = 0; r < n; r++) { mat[r] = []; for (var c = 0; c < n; c++) mat[r][c] = d.isDark(r, c); } 73 this._matrix = mat; this._count = n; 74 } catch (e) { console.error('[SBQR]', e); } 75 document.body.removeChild(tmp); 76 }; 77 78 /* âââ Paint âââââââââââââââââââââââââââââââââââ */ 79 SBQRRenderer.prototype._paint = function () { 80 if (!this._matrix) return; 81 var container = this._container, opts = this.opts; 82 var rawW = container.clientWidth || container.offsetWidth || opts.maxSize; 83 84 // If explicit width/height are provided, use them; otherwise use maxSize logic 85 var size; 86 if (opts.width && opts.width > 0) { 87 size = opts.width; 88 } else { 89 size = Math.min(rawW, opts.maxSize); 90 if (size < 60) size = 60; 91 } 92 this._currentSize = size; 93 94 var canvasH = (opts.height && opts.height > 0) ? opts.height : size; 95 96 var dpr = win.devicePixelRatio || 1; 97 var canvas = this._canvas; 98 if (!canvas) { 99 canvas = document.createElement('canvas'); 100 canvas.style.cssText = 'display:block;width:100%;height:auto;'; 101 this._canvas = canvas; 102 container.innerHTML = ''; 103 container.appendChild(canvas); 104 } 105 canvas.width = size * dpr; 106 canvas.height = canvasH * dpr; 107 108 var ctx = canvas.getContext('2d');
109 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 110 this._draw(ctx, size); 111 }; 112 113 SBQRRenderer.prototype._watchResize = function () { 114 var self = this; 115 if (typeof ResizeObserver !== 'undefined') { 116 this._ro = new ResizeObserver(function () { self._paint(); }); 117 this._ro.observe(this._container); 118 } 119 }; 120 121 /* âââ Draw ââââââââââââââââââââââââââââââââââââ */ 122 SBQRRenderer.prototype._draw = function (ctx, size) { 123 var opts = this.opts, n = this._count, mat = this._matrix; 124 var mgn = opts.margin; 125 var ms = (size - mgn * 2) / n; 126 127 // Couleurs effectives (avec inversion) 128 var cDark = opts.invert ? opts.colorLight : opts.colorDark; 129 var cLight = opts.invert ? opts.colorDark : opts.colorLight; 130 131 // Fond 132 if (opts.qrCornerRadius > 0) { 133 ctx.save(); 134 this._clip(ctx, 0, 0, size, size, opts.qrCornerRadius * (size / 100)); 135 } 136 if (!opts.transparentBg) { ctx.fillStyle = cLight; ctx.fillRect(0, 0, size, size); } 137 else { ctx.clearRect(0, 0, size, size); } 138 139 // Gradient 140 var darkFill = this._buildFill(ctx, opts, size, cDark); 141 142 // Calcul de la zone logo pour exclure les modules en dessous 143 var logoZone = null; 144 if (opts.logoInQR && opts.logoUrl) { 145 var maxL = size * opts.logoSizeRatio; 146 // On ne connaît pas le ratio avant le chargement de l'image, 147 // on utilise le carré maxL à maxL comme zone de sécurité conservative 148 var lSize = maxL; 149 var pad = opts.logoPadding; 150 var bgW = lSize + pad * 2; 151 var bgX = (size - bgW) / 2; 152 var bgY = (size - bgW) / 2; 153 logoZone = { x: bgX, y: bgY, w: bgW }; 154 } 155 156 // Modules données (on saute les modules dans la zone logo) 157 ctx.fillStyle = darkFill; 158 for (var r = 0; r < n; r++) { 159 for (var c = 0; c < n; c++) { 160 if (!mat[r][c]) continue; 161 if (this._isFinder(r, c, n)) continue; 162 // Exclure les modules qui chevauchent la zone logo 163 if (logoZone) { 164 var mx = mgn + c * ms, my = mgn + r * ms; 165 if (mx + ms > logoZone.x && mx < logoZone.x + logoZone.w && 166 my + ms > logoZone.y && my < logoZone.y + logoZone.w) { 167 continue; 168 } 169 } 170 this._drawModule(ctx, mgn + c * ms, mgn + r * ms, ms, opts.moduleStyle, opts.moduleGap); 171 } 172 } 173 174 // Coins stylisés 175 var eyeColor = opts.eyeColorEnabled ? (opts.invert ? this._invertHex(opts.eyeColor) : opts.eyeColor) : cDark; 176 this._drawAllEyes(ctx, n, ms, mgn, eyeColor, cLight, opts.transparentBg); 177 178 if (opts.qrCornerRadius > 0) ctx.restore(); 179 180 // Logo â toujours dessiné en dernier, par-dessus les modules 181 var self = this; 182 if (opts.logoInQR && opts.logoUrl) { 183 if (this._logoImg) { 184 this._drawLogo(ctx, this._logoImg, size); 185 } else { 186 var img = new Image(); img.crossOrigin = 'anonymous'; 187 img.onload = function () { 188 self._logoImg = img; 189 // Redessiner entièrement pour garantir que le logo est par-dessus tout 190 var c2 = self._canvas.getContext('2d'); 191 c2.setTransform(win.devicePixelRatio || 1, 0, 0, win.devicePixelRatio || 1, 0, 0); 192 self._draw(c2, self._currentSize); 193 }; 194 img.onerror = function () { console.warn('[SBQR] Logo non chargé'); }; 195 img.src = opts.logoUrl; 196 } 197 } 198 }; 199 200 SBQRRenderer.prototype._clip = function (ctx, x, y, w, h, r) { 201 ctx.beginPath(); 202 ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r); 203 ctx.lineTo(x + w, y + h - r); ctx.arcTo(x + w, y + h, x + w - r, y + h, r); 204 ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r); 205 ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r); 206 ctx.closePath(); ctx.clip(); 207 }; 208 209 SBQRRenderer.prototype._buildFill = function (ctx, opts, size, cDark) { 210 if (!opts.gradientEnabled) return cDark; 211 var g, c2 = opts.invert ? this._invertHex(opts.gradientColor2) : opts.gradientColor2; 212 switch (opts.gradientDir) { 213 case 'horizontal': g = ctx.createLinearGradient(0, 0, size, 0); break; 214 case 'vertical': g = ctx.createLinearGradient(0, 0, 0, size); break; 215 case 'radial': g = ctx.createRadialGradient(size/2,size/2,0,size/2,size/2,size/2); break; 216 default: g = ctx.createLinearGradient(0, 0, size, size); 217 } 218 g.addColorStop(0, cDark); g.addColorStop(1, c2); 219 return g; 220 }; 221 222 SBQRRenderer.prototype._isFinder = function (r, c, n) { 223 return (r < 7 && c < 7) || (r < 7 && c >= n - 7) || (r >= n - 7 && c < 7); 224 }; 225 226 /* âââ Module individuel âââââââââââââââââââââââ */ 227 SBQRRenderer.prototype._drawModule = function (ctx, x, y, s, style, gap) { 228 var g = s * (gap / 100); 229 var x2 = x + g / 2, y2 = y + g / 2, s2 = s - g; 230 switch (style) { 231 case 'dot': 232 ctx.beginPath(); 233 ctx.arc(x + s / 2, y + s / 2, s2 / 2, 0, Math.PI * 2); 234 ctx.fill(); 235 break; 236 case 'rounded': 237 this._rrFill(ctx, x2, y2, s2, s2, s2 * 0.3); 238 break; 239 case 'diamond': 240 ctx.save(); 241 ctx.translate(x + s / 2, y + s / 2); 242 ctx.rotate(Math.PI / 4); 243 ctx.fillRect(-s2 * 0.38, -s2 * 0.38, s2 * 0.76, s2 * 0.76);
244 ctx.restore(); 245 break; 246 default: 247 ctx.fillRect(x2, y2, s2, s2); 248 } 249 }; 250 251 /* âââ Finder Patterns âââââââââââââââââââââââââ */ 252 SBQRRenderer.prototype._drawAllEyes = function (ctx, n, ms, margin, color, light, transparent) { 253 var pos = [{r:0,c:0},{r:0,c:n-7},{r:n-7,c:0}]; 254 for (var i = 0; i < 3; i++) { 255 this._drawEye(ctx, margin + pos[i].c * ms, margin + pos[i].r * ms, ms, color, light, transparent); 256 } 257 }; 258 259 SBQRRenderer.prototype._drawEye = function (ctx, x, y, ms, color, light, transparent) { 260 var opts = this.opts, outer = 7 * ms, inner = 3 * ms; 261 var ix = x + 2 * ms, iy = y + 2 * ms; 262 ctx.fillStyle = color; 263 264 switch (opts.eyeOuterStyle) { 265 case 'circle': 266 this._ring(ctx, x + outer/2, y + outer/2, outer/2, outer/2 - ms, color, light, transparent); 267 break; 268 case 'rounded': { 269 var ro = ms * 2; 270 this._rrFill(ctx, x, y, outer, outer, ro); 271 if (transparent) { ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = 'rgba(255,255,255,1)'; } 272 else { ctx.fillStyle = light; } 273 this._rrFill(ctx, x + ms, y + ms, outer - 2*ms, outer - 2*ms, ro * 0.4); 274 ctx.globalCompositeOperation = 'source-over'; 275 ctx.fillStyle = color; 276 break; 277 } 278 case 'pointed': 279 ctx.fillRect(x, y, outer, outer); 280 ctx.fillStyle = transparent ? 'rgba(0,0,0,0)' : light; 281 ctx.fillRect(x + ms, y + ms, outer - 2*ms, outer - 2*ms); 282 ctx.fillStyle = color; 283 break; 284 default: 285 ctx.fillRect(x, y, outer, outer); 286 ctx.fillStyle = transparent ? 'rgba(0,0,0,0)' : light; 287 ctx.fillRect(x + ms, y + ms, outer - 2*ms, outer - 2*ms); 288 ctx.fillStyle = color; 289 } 290 291 switch (opts.eyeInnerStyle) { 292 case 'dot': 293 ctx.beginPath(); ctx.arc(ix + inner/2, iy + inner/2, inner/2, 0, Math.PI*2); ctx.fill(); break; 294 case 'rounded': 295 this._rrFill(ctx, ix, iy, inner, inner, ms * 0.8); break; 296 default: 297 ctx.fillRect(ix, iy, inner, inner); 298 } 299 }; 300 301 SBQRRenderer.prototype._ring = function (ctx, cx, cy, r1, r2, fill, bg, transparent) { 302 ctx.fillStyle = fill; 303 ctx.beginPath(); ctx.arc(cx, cy, r1, 0, Math.PI*2); ctx.fill(); 304 if (transparent) { ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = 'rgba(255,255,255,1)'; } 305 else { ctx.fillStyle = bg; } 306 ctx.beginPath(); ctx.arc(cx, cy, r2, 0, Math.PI*2); ctx.fill(); 307 ctx.globalCompositeOperation = 'source-over'; 308 }; 309 310 /* âââ Logo ââââââââââââââââââââââââââââââââââââ */ 311 SBQRRenderer.prototype._drawLogo = function (ctx, img, size) { 312 var opts = this.opts; 313 // Preserve image aspect ratio 314 var maxL = size * opts.logoSizeRatio; 315 var ratio = (img.naturalWidth && img.naturalHeight) ? img.naturalWidth / img.naturalHeight : 1; 316 var lW, lH; 317 if (ratio >= 1) { lW = maxL; lH = maxL / ratio; } 318 else { lH = maxL; lW = maxL * ratio; } 319 320 var lx = (size - lW) / 2, ly = (size - lH) / 2; 321 var pad = opts.logoPadding; 322 var bgW = lW + pad * 2, bgH = lH + pad * 2; 323 var bgX = lx - pad, bgY = ly - pad; 324 325 if (!opts.logoBgTransparent) { 326 ctx.fillStyle = opts.logoBgColor || '#ffffff'; 327 this._rrFill(ctx, bgX, bgY, bgW, bgH, opts.logoBgRadius); 328 } 329 ctx.drawImage(img, lx, ly, lW, lH); 330 }; 331 332 /* âââ Helpers âââââââââââââââââââââââââââââââââ */ 333 SBQRRenderer.prototype._rrFill = function (ctx, x, y, w, h, r) { 334 r = Math.max(0, Math.min(r, w/2, h/2)); 335 ctx.beginPath(); 336 ctx.moveTo(x+r, y); ctx.lineTo(x+w-r, y); ctx.arcTo(x+w, y, x+w, y+r, r); 337 ctx.lineTo(x+w, y+h-r); ctx.arcTo(x+w, y+h, x+w-r, y+h, r); 338 ctx.lineTo(x+r, y+h); ctx.arcTo(x, y+h, x, y+h-r, r); 339 ctx.lineTo(x, y+r); ctx.arcTo(x, y, x+r, y, r); 340 ctx.closePath(); ctx.fill(); 341 }; 342 343 SBQRRenderer.prototype._invertHex = function (hex) { 344 hex = hex.replace('#', ''); 345 if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; 346 var r = 255 - parseInt(hex.slice(0,2),16); 347 var g = 255 - parseInt(hex.slice(2,4),16); 348 var b = 255 - parseInt(hex.slice(4,6),16); 349 return '#' + [r,g,b].map(function(v){return ('0'+v.toString(16)).slice(-2);}).join(''); 350 }; 351 352 win.SBQRRenderer = SBQRRenderer; 353})(window);
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.