jdenticon-3.3.0.js (51424B)
1 /** 2 * Jdenticon 3.3.0 3 * http://jdenticon.com 4 * 5 * Built: 2024-05-10T09:48:41.921Z 6 * 7 * MIT License 8 * 9 * Copyright (c) 2014-2024 Daniel Mester Pirttijärvi 10 * 11 * Permission is hereby granted, free of charge, to any person obtaining a copy 12 * of this software and associated documentation files (the "Software"), to deal 13 * in the Software without restriction, including without limitation the rights 14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 * copies of the Software, and to permit persons to whom the Software is 16 * furnished to do so, subject to the following conditions: 17 * 18 * The above copyright notice and this permission notice shall be included in all 19 * copies or substantial portions of the Software. 20 * 21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 * SOFTWARE. 28 */ 29 30 (function (umdGlobal, factory) { 31 var jdenticon = factory(umdGlobal); 32 33 // Node.js 34 if (typeof module !== "undefined" && "exports" in module) { 35 module["exports"] = jdenticon; 36 } 37 // RequireJS 38 else if (typeof define === "function" && define["amd"]) { 39 define([], function () { return jdenticon; }); 40 } 41 // No module loader 42 else { 43 umdGlobal["jdenticon"] = jdenticon; 44 } 45 })(typeof self !== "undefined" ? self : this, function (umdGlobal) { 46 'use strict'; 47 48 /** 49 * Parses a substring of the hash as a number. 50 * @param {number} startPosition 51 * @param {number=} octets 52 */ 53 function parseHex(hash, startPosition, octets) { 54 return parseInt(hash.substr(startPosition, octets), 16); 55 } 56 57 function decToHex(v) { 58 v |= 0; // Ensure integer value 59 return v < 0 ? "00" : 60 v < 16 ? "0" + v.toString(16) : 61 v < 256 ? v.toString(16) : 62 "ff"; 63 } 64 65 function hueToRgb(m1, m2, h) { 66 h = h < 0 ? h + 6 : h > 6 ? h - 6 : h; 67 return decToHex(255 * ( 68 h < 1 ? m1 + (m2 - m1) * h : 69 h < 3 ? m2 : 70 h < 4 ? m1 + (m2 - m1) * (4 - h) : 71 m1)); 72 } 73 74 /** 75 * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported. 76 * @returns {string} 77 */ 78 function parseColor(color) { 79 if (/^#[0-9a-f]{3,8}$/i.test(color)) { 80 var result; 81 var colorLength = color.length; 82 83 if (colorLength < 6) { 84 var r = color[1], 85 g = color[2], 86 b = color[3], 87 a = color[4] || ""; 88 result = "#" + r + r + g + g + b + b + a + a; 89 } 90 if (colorLength == 7 || colorLength > 8) { 91 result = color; 92 } 93 94 return result; 95 } 96 } 97 98 /** 99 * Converts a hexadecimal color to a CSS3 compatible color. 100 * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA" 101 * @returns {string} 102 */ 103 function toCss3Color(hexColor) { 104 var a = parseHex(hexColor, 7, 2); 105 var result; 106 107 if (isNaN(a)) { 108 result = hexColor; 109 } else { 110 var r = parseHex(hexColor, 1, 2), 111 g = parseHex(hexColor, 3, 2), 112 b = parseHex(hexColor, 5, 2); 113 result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")"; 114 } 115 116 return result; 117 } 118 119 /** 120 * Converts an HSL color to a hexadecimal RGB color. 121 * @param {number} hue Hue in range [0, 1] 122 * @param {number} saturation Saturation in range [0, 1] 123 * @param {number} lightness Lightness in range [0, 1] 124 * @returns {string} 125 */ 126 function hsl(hue, saturation, lightness) { 127 // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color 128 var result; 129 130 if (saturation == 0) { 131 var partialHex = decToHex(lightness * 255); 132 result = partialHex + partialHex + partialHex; 133 } 134 else { 135 var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation, 136 m1 = lightness * 2 - m2; 137 result = 138 hueToRgb(m1, m2, hue * 6 + 2) + 139 hueToRgb(m1, m2, hue * 6) + 140 hueToRgb(m1, m2, hue * 6 - 2); 141 } 142 143 return "#" + result; 144 } 145 146 /** 147 * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues 148 * @param {number} hue Hue in range [0, 1] 149 * @param {number} saturation Saturation in range [0, 1] 150 * @param {number} lightness Lightness in range [0, 1] 151 * @returns {string} 152 */ 153 function correctedHsl(hue, saturation, lightness) { 154 // The corrector specifies the perceived middle lightness for each hue 155 var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ], 156 corrector = correctors[(hue * 6 + 0.5) | 0]; 157 158 // Adjust the input lightness relative to the corrector 159 lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2; 160 161 return hsl(hue, saturation, lightness); 162 } 163 164 /* global umdGlobal */ 165 166 // In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for 167 // backward compatibility. 168 var GLOBAL = umdGlobal; 169 170 /** 171 * @typedef {Object} ParsedConfiguration 172 * @property {number} colorSaturation 173 * @property {number} grayscaleSaturation 174 * @property {string} backColor 175 * @property {number} iconPadding 176 * @property {function(number):number} hue 177 * @property {function(number):number} colorLightness 178 * @property {function(number):number} grayscaleLightness 179 */ 180 181 var CONFIG_PROPERTIES = { 182 G/*GLOBAL*/: "jdenticon_config", 183 n/*MODULE*/: "config", 184 }; 185 186 var rootConfigurationHolder = {}; 187 188 /** 189 * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console 190 * when it is being used. 191 * @param {!Object} rootObject 192 */ 193 function defineConfigProperty(rootObject) { 194 rootConfigurationHolder = rootObject; 195 } 196 197 /** 198 * Sets a new icon style configuration. The new configuration is not merged with the previous one. * 199 * @param {Object} newConfiguration - New configuration object. 200 */ 201 function configure(newConfiguration) { 202 if (arguments.length) { 203 rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration; 204 } 205 return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/]; 206 } 207 208 /** 209 * Gets the normalized current Jdenticon color configuration. Missing fields have default values. 210 * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A 211 * local configuration overrides the global configuration in it entirety. This parameter can for backward 212 * compatibility also contain a padding value. A padding value only overrides the global padding, not the 213 * entire global configuration. 214 * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor 215 * explicitly to the API method. 216 * @returns {ParsedConfiguration} 217 */ 218 function getConfiguration(paddingOrLocalConfig, defaultPadding) { 219 var configObject = 220 typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig || 221 rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] || 222 GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] || 223 { }, 224 225 lightnessConfig = configObject["lightness"] || { }, 226 227 // In versions < 2.1.0 there was no grayscale saturation - 228 // saturation was the color saturation. 229 saturation = configObject["saturation"] || { }, 230 colorSaturation = "color" in saturation ? saturation["color"] : saturation, 231 grayscaleSaturation = saturation["grayscale"], 232 233 backColor = configObject["backColor"], 234 padding = configObject["padding"]; 235 236 /** 237 * Creates a lightness range. 238 */ 239 function lightness(configName, defaultRange) { 240 var range = lightnessConfig[configName]; 241 242 // Check if the lightness range is an array-like object. This way we ensure the 243 // array contain two values at the same time. 244 if (!(range && range.length > 1)) { 245 range = defaultRange; 246 } 247 248 /** 249 * Gets a lightness relative the specified value in the specified lightness range. 250 */ 251 return function (value) { 252 value = range[0] + value * (range[1] - range[0]); 253 return value < 0 ? 0 : value > 1 ? 1 : value; 254 }; 255 } 256 257 /** 258 * Gets a hue allowed by the configured hue restriction, 259 * provided the originally computed hue. 260 */ 261 function hueFunction(originalHue) { 262 var hueConfig = configObject["hues"]; 263 var hue; 264 265 // Check if 'hues' is an array-like object. This way we also ensure that 266 // the array is not empty, which would mean no hue restriction. 267 if (hueConfig && hueConfig.length > 0) { 268 // originalHue is in the range [0, 1] 269 // Multiply with 0.999 to change the range to [0, 1) and then truncate the index. 270 hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)]; 271 } 272 273 return typeof hue == "number" ? 274 275 // A hue was specified. We need to convert the hue from 276 // degrees on any turn - e.g. 746° is a perfectly valid hue - 277 // to turns in the range [0, 1). 278 ((((hue / 360) % 1) + 1) % 1) : 279 280 // No hue configured => use original hue 281 originalHue; 282 } 283 284 return { 285 X/*hue*/: hueFunction, 286 p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5, 287 H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0, 288 q/*colorLightness*/: lightness("color", [0.4, 0.8]), 289 I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]), 290 J/*backColor*/: parseColor(backColor), 291 Y/*iconPadding*/: 292 typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig : 293 typeof padding == "number" ? padding : 294 defaultPadding 295 } 296 } 297 298 var ICON_TYPE_SVG = 1; 299 300 var ICON_TYPE_CANVAS = 2; 301 302 var ATTRIBUTES = { 303 t/*HASH*/: "data-jdenticon-hash", 304 o/*VALUE*/: "data-jdenticon-value" 305 }; 306 307 var IS_RENDERED_PROPERTY = "jdenticonRendered"; 308 309 var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]"; 310 311 var documentQuerySelectorAll = /** @type {!Function} */ ( 312 typeof document !== "undefined" && document.querySelectorAll.bind(document)); 313 314 function getIdenticonType(el) { 315 if (el) { 316 var tagName = el["tagName"]; 317 318 if (/^svg$/i.test(tagName)) { 319 return ICON_TYPE_SVG; 320 } 321 322 if (/^canvas$/i.test(tagName) && "getContext" in el) { 323 return ICON_TYPE_CANVAS; 324 } 325 } 326 } 327 328 function whenDocumentIsReady(/** @type {Function} */ callback) { 329 function loadedHandler() { 330 document.removeEventListener("DOMContentLoaded", loadedHandler); 331 window.removeEventListener("load", loadedHandler); 332 setTimeout(callback, 0); // Give scripts a chance to run 333 } 334 335 if (typeof document !== "undefined" && 336 typeof window !== "undefined" && 337 typeof setTimeout !== "undefined" 338 ) { 339 if (document.readyState === "loading") { 340 document.addEventListener("DOMContentLoaded", loadedHandler); 341 window.addEventListener("load", loadedHandler); 342 } else { 343 // Document already loaded. The load events above likely won't be raised 344 setTimeout(callback, 0); 345 } 346 } 347 } 348 349 function observer(updateCallback) { 350 if (typeof MutationObserver != "undefined") { 351 var mutationObserver = new MutationObserver(function onmutation(mutations) { 352 for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) { 353 var mutation = mutations[mutationIndex]; 354 var addedNodes = mutation.addedNodes; 355 356 for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) { 357 var addedNode = addedNodes[addedNodeIndex]; 358 359 // Skip other types of nodes than element nodes, since they might not support 360 // the querySelectorAll method => runtime error. 361 if (addedNode.nodeType == 1) { 362 if (getIdenticonType(addedNode)) { 363 updateCallback(addedNode); 364 } 365 else { 366 var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR); 367 for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) { 368 updateCallback(icons[iconIndex]); 369 } 370 } 371 } 372 } 373 374 if (mutation.type == "attributes" && getIdenticonType(mutation.target)) { 375 updateCallback(mutation.target); 376 } 377 } 378 }); 379 380 mutationObserver.observe(document.body, { 381 "childList": true, 382 "attributes": true, 383 "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"], 384 "subtree": true, 385 }); 386 } 387 } 388 389 /** 390 * Represents a point. 391 */ 392 function Point(x, y) { 393 this.x = x; 394 this.y = y; 395 } 396 397 /** 398 * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself, 399 * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly. 400 */ 401 function Transform(x, y, size, rotation) { 402 this.u/*_x*/ = x; 403 this.v/*_y*/ = y; 404 this.K/*_size*/ = size; 405 this.Z/*_rotation*/ = rotation; 406 } 407 408 /** 409 * Transforms the specified point based on the translation and rotation specification for this Transform. 410 * @param {number} x x-coordinate 411 * @param {number} y y-coordinate 412 * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. 413 * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. 414 */ 415 Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) { 416 var right = this.u/*_x*/ + this.K/*_size*/, 417 bottom = this.v/*_y*/ + this.K/*_size*/, 418 rotation = this.Z/*_rotation*/; 419 return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) : 420 rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) : 421 rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) : 422 new Point(this.u/*_x*/ + x, this.v/*_y*/ + y); 423 }; 424 425 var NO_TRANSFORM = new Transform(0, 0, 0, 0); 426 427 428 429 /** 430 * Provides helper functions for rendering common basic shapes. 431 */ 432 function Graphics(renderer) { 433 /** 434 * @type {Renderer} 435 * @private 436 */ 437 this.M/*_renderer*/ = renderer; 438 439 /** 440 * @type {Transform} 441 */ 442 this.A/*currentTransform*/ = NO_TRANSFORM; 443 } 444 var Graphics__prototype = Graphics.prototype; 445 446 /** 447 * Adds a polygon to the underlying renderer. 448 * @param {Array<number>} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ] 449 * @param {boolean=} invert Specifies if the polygon will be inverted. 450 */ 451 Graphics__prototype.g/*addPolygon*/ = function addPolygon (points, invert) { 452 var this$1 = this; 453 454 var di = invert ? -2 : 2, 455 transformedPoints = []; 456 457 for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) { 458 transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1])); 459 } 460 461 this.M/*_renderer*/.g/*addPolygon*/(transformedPoints); 462 }; 463 464 /** 465 * Adds a polygon to the underlying renderer. 466 * Source: http://stackoverflow.com/a/2173084 467 * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse. 468 * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse. 469 * @param {number} size The size of the ellipse. 470 * @param {boolean=} invert Specifies if the ellipse will be inverted. 471 */ 472 Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) { 473 var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size); 474 this.M/*_renderer*/.h/*addCircle*/(p, size, invert); 475 }; 476 477 /** 478 * Adds a rectangle to the underlying renderer. 479 * @param {number} x The x-coordinate of the upper left corner of the rectangle. 480 * @param {number} y The y-coordinate of the upper left corner of the rectangle. 481 * @param {number} w The width of the rectangle. 482 * @param {number} h The height of the rectangle. 483 * @param {boolean=} invert Specifies if the rectangle will be inverted. 484 */ 485 Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) { 486 this.g/*addPolygon*/([ 487 x, y, 488 x + w, y, 489 x + w, y + h, 490 x, y + h 491 ], invert); 492 }; 493 494 /** 495 * Adds a right triangle to the underlying renderer. 496 * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle. 497 * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle. 498 * @param {number} w The width of the triangle. 499 * @param {number} h The height of the triangle. 500 * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle. 501 * @param {boolean=} invert Specifies if the triangle will be inverted. 502 */ 503 Graphics__prototype.j/*addTriangle*/ = function addTriangle (x, y, w, h, r, invert) { 504 var points = [ 505 x + w, y, 506 x + w, y + h, 507 x, y + h, 508 x, y 509 ]; 510 points.splice(((r || 0) % 4) * 2, 2); 511 this.g/*addPolygon*/(points, invert); 512 }; 513 514 /** 515 * Adds a rhombus to the underlying renderer. 516 * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus. 517 * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus. 518 * @param {number} w The width of the rhombus. 519 * @param {number} h The height of the rhombus. 520 * @param {boolean=} invert Specifies if the rhombus will be inverted. 521 */ 522 Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) { 523 this.g/*addPolygon*/([ 524 x + w / 2, y, 525 x + w, y + h / 2, 526 x + w / 2, y + h, 527 x, y + h / 2 528 ], invert); 529 }; 530 531 /** 532 * @param {number} index 533 * @param {Graphics} g 534 * @param {number} cell 535 * @param {number} positionIndex 536 */ 537 function centerShape(index, g, cell, positionIndex) { 538 index = index % 14; 539 540 var k, m, w, h, inner, outer; 541 542 !index ? ( 543 k = cell * 0.42, 544 g.g/*addPolygon*/([ 545 0, 0, 546 cell, 0, 547 cell, cell - k * 2, 548 cell - k, cell, 549 0, cell 550 ])) : 551 552 index == 1 ? ( 553 w = 0 | (cell * 0.5), 554 h = 0 | (cell * 0.8), 555 556 g.j/*addTriangle*/(cell - w, 0, w, h, 2)) : 557 558 index == 2 ? ( 559 w = 0 | (cell / 3), 560 g.i/*addRectangle*/(w, w, cell - w, cell - w)) : 561 562 index == 3 ? ( 563 inner = cell * 0.1, 564 // Use fixed outer border widths in small icons to ensure the border is drawn 565 outer = 566 cell < 6 ? 1 : 567 cell < 8 ? 2 : 568 (0 | (cell * 0.25)), 569 570 inner = 571 inner > 1 ? (0 | inner) : // large icon => truncate decimals 572 inner > 0.5 ? 1 : // medium size icon => fixed width 573 inner, // small icon => anti-aliased border 574 575 g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) : 576 577 index == 4 ? ( 578 m = 0 | (cell * 0.15), 579 w = 0 | (cell * 0.5), 580 g.h/*addCircle*/(cell - w - m, cell - w - m, w)) : 581 582 index == 5 ? ( 583 inner = cell * 0.1, 584 outer = inner * 4, 585 586 // Align edge to nearest pixel in large icons 587 outer > 3 && (outer = 0 | outer), 588 589 g.i/*addRectangle*/(0, 0, cell, cell), 590 g.g/*addPolygon*/([ 591 outer, outer, 592 cell - inner, outer, 593 outer + (cell - outer - inner) / 2, cell - inner 594 ], true)) : 595 596 index == 6 ? 597 g.g/*addPolygon*/([ 598 0, 0, 599 cell, 0, 600 cell, cell * 0.7, 601 cell * 0.4, cell * 0.4, 602 cell * 0.7, cell, 603 0, cell 604 ]) : 605 606 index == 7 ? 607 g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : 608 609 index == 8 ? ( 610 g.i/*addRectangle*/(0, 0, cell, cell / 2), 611 g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2), 612 g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) : 613 614 index == 9 ? ( 615 inner = cell * 0.14, 616 // Use fixed outer border widths in small icons to ensure the border is drawn 617 outer = 618 cell < 4 ? 1 : 619 cell < 6 ? 2 : 620 (0 | (cell * 0.35)), 621 622 inner = 623 cell < 8 ? inner : // small icon => anti-aliased border 624 (0 | inner), // large icon => truncate decimals 625 626 g.i/*addRectangle*/(0, 0, cell, cell), 627 g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) : 628 629 index == 10 ? ( 630 inner = cell * 0.12, 631 outer = inner * 3, 632 633 g.i/*addRectangle*/(0, 0, cell, cell), 634 g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) : 635 636 index == 11 ? 637 g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : 638 639 index == 12 ? ( 640 m = cell * 0.25, 641 g.i/*addRectangle*/(0, 0, cell, cell), 642 g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) : 643 644 // 13 645 ( 646 !positionIndex && ( 647 m = cell * 0.4, w = cell * 1.2, 648 g.h/*addCircle*/(m, m, w) 649 ) 650 ); 651 } 652 653 /** 654 * @param {number} index 655 * @param {Graphics} g 656 * @param {number} cell 657 */ 658 function outerShape(index, g, cell) { 659 index = index % 4; 660 661 var m; 662 663 !index ? 664 g.j/*addTriangle*/(0, 0, cell, cell, 0) : 665 666 index == 1 ? 667 g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) : 668 669 index == 2 ? 670 g.N/*addRhombus*/(0, 0, cell, cell) : 671 672 // 3 673 ( 674 m = cell / 6, 675 g.h/*addCircle*/(m, m, cell - 2 * m) 676 ); 677 } 678 679 /** 680 * Gets a set of identicon color candidates for a specified hue and config. 681 * @param {number} hue 682 * @param {ParsedConfiguration} config 683 */ 684 function colorTheme(hue, config) { 685 hue = config.X/*hue*/(hue); 686 return [ 687 // Dark gray 688 correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)), 689 // Mid color 690 correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)), 691 // Light gray 692 correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)), 693 // Light color 694 correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)), 695 // Dark color 696 correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0)) 697 ]; 698 } 699 700 /** 701 * Draws an identicon to a specified renderer. 702 * @param {Renderer} renderer 703 * @param {string} hash 704 * @param {Object|number=} config 705 */ 706 function iconGenerator(renderer, hash, config) { 707 var parsedConfig = getConfiguration(config, 0.08); 708 709 // Set background color 710 if (parsedConfig.J/*backColor*/) { 711 renderer.m/*setBackground*/(parsedConfig.J/*backColor*/); 712 } 713 714 // Calculate padding and round to nearest integer 715 var size = renderer.k/*iconSize*/; 716 var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0; 717 size -= padding * 2; 718 719 var graphics = new Graphics(renderer); 720 721 // Calculate cell size and ensure it is an integer 722 var cell = 0 | (size / 4); 723 724 // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon 725 var x = 0 | (padding + size / 2 - cell * 2); 726 var y = 0 | (padding + size / 2 - cell * 2); 727 728 function renderShape(colorIndex, shapes, index, rotationIndex, positions) { 729 var shapeIndex = parseHex(hash, index, 1); 730 var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0; 731 732 renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]); 733 734 for (var i = 0; i < positions.length; i++) { 735 graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4); 736 shapes(shapeIndex, graphics, cell, i); 737 } 738 739 renderer.P/*endShape*/(); 740 } 741 742 // AVAILABLE COLORS 743 var hue = parseHex(hash, -7) / 0xfffffff, 744 745 // Available colors for this icon 746 availableColors = colorTheme(hue, parsedConfig), 747 748 // The index of the selected colors 749 selectedColorIndexes = []; 750 751 var index; 752 753 function isDuplicate(values) { 754 if (values.indexOf(index) >= 0) { 755 for (var i = 0; i < values.length; i++) { 756 if (selectedColorIndexes.indexOf(values[i]) >= 0) { 757 return true; 758 } 759 } 760 } 761 } 762 763 for (var i = 0; i < 3; i++) { 764 index = parseHex(hash, 8 + i, 1) % availableColors.length; 765 if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo 766 isDuplicate([2, 3])) { // Disallow light gray and light color combo 767 index = 1; 768 } 769 selectedColorIndexes.push(index); 770 } 771 772 // ACTUAL RENDERING 773 // Sides 774 renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]); 775 // Corners 776 renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]); 777 // Center 778 renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]); 779 780 renderer.finish(); 781 } 782 783 /** 784 * Computes a SHA1 hash for any value and returns it as a hexadecimal string. 785 * 786 * This function is optimized for minimal code size and rather short messages. 787 * 788 * @param {string} message 789 */ 790 function sha1(message) { 791 var HASH_SIZE_HALF_BYTES = 40; 792 var BLOCK_SIZE_WORDS = 16; 793 794 // Variables 795 // `var` is used to be able to minimize the number of `var` keywords. 796 var i = 0, 797 f = 0, 798 799 // Use `encodeURI` to UTF8 encode the message without any additional libraries 800 // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky 801 // since `unescape` is deprecated. 802 urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding 803 804 // This can be changed to a preallocated Uint32Array array for greater performance and larger code size 805 data = [], 806 dataSize, 807 808 hashBuffer = [], 809 810 a = 0x67452301, 811 b = 0xefcdab89, 812 c = ~a, 813 d = ~b, 814 e = 0xc3d2e1f0, 815 hash = [a, b, c, d, e], 816 817 blockStartIndex = 0, 818 hexHash = ""; 819 820 /** 821 * Rotates the value a specified number of bits to the left. 822 * @param {number} value Value to rotate 823 * @param {number} shift Bit count to shift. 824 */ 825 function rotl(value, shift) { 826 return (value << shift) | (value >>> (32 - shift)); 827 } 828 829 // Message data 830 for ( ; i < urlEncodedMessage.length; f++) { 831 data[f >> 2] = data[f >> 2] | 832 ( 833 ( 834 urlEncodedMessage[i] == "%" 835 // Percent encoded byte 836 ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16) 837 // Unencoded byte 838 : urlEncodedMessage.charCodeAt(i++) 839 ) 840 841 // Read bytes in reverse order (big endian words) 842 << ((3 - (f & 3)) * 8) 843 ); 844 } 845 846 // f is now the length of the utf8 encoded message 847 // 7 = 8 bytes (64 bit) for message size, -1 to round down 848 // >> 6 = integer division with block size 849 dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS; 850 851 // Message size in bits. 852 // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least 853 // significant 32 bits are set. -8 is for the '1' bit padding byte. 854 data[dataSize - 1] = f * 8 - 8; 855 856 // Compute hash 857 for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) { 858 for (i = 0; i < 80; i++) { 859 f = rotl(a, 5) + e + ( 860 // Ch 861 i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 : 862 863 // Parity 864 i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 : 865 866 // Maj 867 i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc : 868 869 // Parity 870 (b ^ c ^ d) + 0xca62c1d6 871 ) + ( 872 hashBuffer[i] = i < BLOCK_SIZE_WORDS 873 // Bitwise OR is used to coerse `undefined` to 0 874 ? (data[blockStartIndex + i] | 0) 875 : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1) 876 ); 877 878 e = d; 879 d = c; 880 c = rotl(b, 30); 881 b = a; 882 a = f; 883 } 884 885 hash[0] = a = ((hash[0] + a) | 0); 886 hash[1] = b = ((hash[1] + b) | 0); 887 hash[2] = c = ((hash[2] + c) | 0); 888 hash[3] = d = ((hash[3] + d) | 0); 889 hash[4] = e = ((hash[4] + e) | 0); 890 } 891 892 // Format hex hash 893 for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) { 894 hexHash += ( 895 ( 896 // Get word (2^3 half-bytes per word) 897 hash[i >> 3] >>> 898 899 // Append half-bytes in reverse order 900 ((7 - (i & 7)) * 4) 901 ) 902 // Clamp to half-byte 903 & 0xf 904 ).toString(16); 905 } 906 907 return hexHash; 908 } 909 910 /** 911 * Inputs a value that might be a valid hash string for Jdenticon and returns it 912 * if it is determined valid, otherwise a falsy value is returned. 913 */ 914 function isValidHash(hashCandidate) { 915 return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate; 916 } 917 918 /** 919 * Computes a hash for the specified value. Currently SHA1 is used. This function 920 * always returns a valid hash. 921 */ 922 function computeHash(value) { 923 return sha1(value == null ? "" : "" + value); 924 } 925 926 927 928 /** 929 * Renderer redirecting drawing commands to a canvas context. 930 * @implements {Renderer} 931 */ 932 function CanvasRenderer(ctx, iconSize) { 933 var canvas = ctx.canvas; 934 var width = canvas.width; 935 var height = canvas.height; 936 937 ctx.save(); 938 939 if (!iconSize) { 940 iconSize = Math.min(width, height); 941 942 ctx.translate( 943 ((width - iconSize) / 2) | 0, 944 ((height - iconSize) / 2) | 0); 945 } 946 947 /** 948 * @private 949 */ 950 this.l/*_ctx*/ = ctx; 951 this.k/*iconSize*/ = iconSize; 952 953 ctx.clearRect(0, 0, iconSize, iconSize); 954 } 955 var CanvasRenderer__prototype = CanvasRenderer.prototype; 956 957 /** 958 * Fills the background with the specified color. 959 * @param {string} fillColor Fill color on the format #rrggbb[aa]. 960 */ 961 CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) { 962 var ctx = this.l/*_ctx*/; 963 var iconSize = this.k/*iconSize*/; 964 965 ctx.fillStyle = toCss3Color(fillColor); 966 ctx.fillRect(0, 0, iconSize, iconSize); 967 }; 968 969 /** 970 * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. 971 * @param {string} fillColor Fill color on format #rrggbb[aa]. 972 */ 973 CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) { 974 var ctx = this.l/*_ctx*/; 975 ctx.fillStyle = toCss3Color(fillColor); 976 ctx.beginPath(); 977 }; 978 979 /** 980 * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas. 981 */ 982 CanvasRenderer__prototype.P/*endShape*/ = function endShape () { 983 this.l/*_ctx*/.fill(); 984 }; 985 986 /** 987 * Adds a polygon to the rendering queue. 988 * @param points An array of Point objects. 989 */ 990 CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) { 991 var ctx = this.l/*_ctx*/; 992 ctx.moveTo(points[0].x, points[0].y); 993 for (var i = 1; i < points.length; i++) { 994 ctx.lineTo(points[i].x, points[i].y); 995 } 996 ctx.closePath(); 997 }; 998 999 /** 1000 * Adds a circle to the rendering queue. 1001 * @param {Point} point The upper left corner of the circle bounding box. 1002 * @param {number} diameter The diameter of the circle. 1003 * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). 1004 */ 1005 CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { 1006 var ctx = this.l/*_ctx*/, 1007 radius = diameter / 2; 1008 ctx.moveTo(point.x + radius, point.y + radius); 1009 ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise); 1010 ctx.closePath(); 1011 }; 1012 1013 /** 1014 * Called when the icon has been completely drawn. 1015 */ 1016 CanvasRenderer__prototype.finish = function finish () { 1017 this.l/*_ctx*/.restore(); 1018 }; 1019 1020 /** 1021 * Draws an identicon to a context. 1022 * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0). 1023 * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. 1024 * @param {number} size - Icon size in pixels. 1025 * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any 1026 * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be 1027 * specified in place of a configuration object. 1028 */ 1029 function drawIcon(ctx, hashOrValue, size, config) { 1030 if (!ctx) { 1031 throw new Error("No canvas specified."); 1032 } 1033 1034 iconGenerator(new CanvasRenderer(ctx, size), 1035 isValidHash(hashOrValue) || computeHash(hashOrValue), 1036 config); 1037 1038 var canvas = ctx.canvas; 1039 if (canvas) { 1040 canvas[IS_RENDERED_PROPERTY] = true; 1041 } 1042 } 1043 1044 /** 1045 * Prepares a measure to be used as a measure in an SVG path, by 1046 * rounding the measure to a single decimal. This reduces the file 1047 * size of the generated SVG with more than 50% in some cases. 1048 */ 1049 function svgValue(value) { 1050 return ((value * 10 + 0.5) | 0) / 10; 1051 } 1052 1053 /** 1054 * Represents an SVG path element. 1055 */ 1056 function SvgPath() { 1057 /** 1058 * This property holds the data string (path.d) of the SVG path. 1059 * @type {string} 1060 */ 1061 this.B/*dataString*/ = ""; 1062 } 1063 var SvgPath__prototype = SvgPath.prototype; 1064 1065 /** 1066 * Adds a polygon with the current fill color to the SVG path. 1067 * @param points An array of Point objects. 1068 */ 1069 SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) { 1070 var dataString = ""; 1071 for (var i = 0; i < points.length; i++) { 1072 dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y); 1073 } 1074 this.B/*dataString*/ += dataString + "Z"; 1075 }; 1076 1077 /** 1078 * Adds a circle with the current fill color to the SVG path. 1079 * @param {Point} point The upper left corner of the circle bounding box. 1080 * @param {number} diameter The diameter of the circle. 1081 * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). 1082 */ 1083 SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { 1084 var sweepFlag = counterClockwise ? 0 : 1, 1085 svgRadius = svgValue(diameter / 2), 1086 svgDiameter = svgValue(diameter), 1087 svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " "; 1088 1089 this.B/*dataString*/ += 1090 "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) + 1091 svgArc + svgDiameter + ",0" + 1092 svgArc + (-svgDiameter) + ",0"; 1093 }; 1094 1095 1096 1097 /** 1098 * Renderer producing SVG output. 1099 * @implements {Renderer} 1100 */ 1101 function SvgRenderer(target) { 1102 /** 1103 * @type {SvgPath} 1104 * @private 1105 */ 1106 this.C/*_path*/; 1107 1108 /** 1109 * @type {Object.<string,SvgPath>} 1110 * @private 1111 */ 1112 this.D/*_pathsByColor*/ = { }; 1113 1114 /** 1115 * @type {SvgElement|SvgWriter} 1116 * @private 1117 */ 1118 this.R/*_target*/ = target; 1119 1120 /** 1121 * @type {number} 1122 */ 1123 this.k/*iconSize*/ = target.k/*iconSize*/; 1124 } 1125 var SvgRenderer__prototype = SvgRenderer.prototype; 1126 1127 /** 1128 * Fills the background with the specified color. 1129 * @param {string} fillColor Fill color on the format #rrggbb[aa]. 1130 */ 1131 SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) { 1132 var match = /^(#......)(..)?/.exec(fillColor), 1133 opacity = match[2] ? parseHex(match[2], 0) / 255 : 1; 1134 this.R/*_target*/.m/*setBackground*/(match[1], opacity); 1135 }; 1136 1137 /** 1138 * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. 1139 * @param {string} color Fill color on format #xxxxxx. 1140 */ 1141 SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) { 1142 this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath()); 1143 }; 1144 1145 /** 1146 * Marks the end of the currently drawn shape. 1147 */ 1148 SvgRenderer__prototype.P/*endShape*/ = function endShape () { }; 1149 1150 /** 1151 * Adds a polygon with the current fill color to the SVG. 1152 * @param points An array of Point objects. 1153 */ 1154 SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) { 1155 this.C/*_path*/.g/*addPolygon*/(points); 1156 }; 1157 1158 /** 1159 * Adds a circle with the current fill color to the SVG. 1160 * @param {Point} point The upper left corner of the circle bounding box. 1161 * @param {number} diameter The diameter of the circle. 1162 * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). 1163 */ 1164 SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { 1165 this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise); 1166 }; 1167 1168 /** 1169 * Called when the icon has been completely drawn. 1170 */ 1171 SvgRenderer__prototype.finish = function finish () { 1172 var this$1 = this; 1173 1174 var pathsByColor = this.D/*_pathsByColor*/; 1175 for (var color in pathsByColor) { 1176 // hasOwnProperty cannot be shadowed in pathsByColor 1177 // eslint-disable-next-line no-prototype-builtins 1178 if (pathsByColor.hasOwnProperty(color)) { 1179 this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/); 1180 } 1181 } 1182 }; 1183 1184 var SVG_CONSTANTS = { 1185 T/*XMLNS*/: "http://www.w3.org/2000/svg", 1186 U/*WIDTH*/: "width", 1187 V/*HEIGHT*/: "height", 1188 }; 1189 1190 /** 1191 * Renderer producing SVG output. 1192 */ 1193 function SvgWriter(iconSize) { 1194 /** 1195 * @type {number} 1196 */ 1197 this.k/*iconSize*/ = iconSize; 1198 1199 /** 1200 * @type {string} 1201 * @private 1202 */ 1203 this.F/*_s*/ = 1204 '<svg xmlns="' + SVG_CONSTANTS.T/*XMLNS*/ + '" width="' + 1205 iconSize + '" height="' + iconSize + '" viewBox="0 0 ' + 1206 iconSize + ' ' + iconSize + '">'; 1207 } 1208 var SvgWriter__prototype = SvgWriter.prototype; 1209 1210 /** 1211 * Fills the background with the specified color. 1212 * @param {string} fillColor Fill color on the format #rrggbb. 1213 * @param {number} opacity Opacity in the range [0.0, 1.0]. 1214 */ 1215 SvgWriter__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) { 1216 if (opacity) { 1217 this.F/*_s*/ += '<rect width="100%" height="100%" fill="' + 1218 fillColor + '" opacity="' + opacity.toFixed(2) + '"/>'; 1219 } 1220 }; 1221 1222 /** 1223 * Writes a path to the SVG string. 1224 * @param {string} color Fill color on format #rrggbb. 1225 * @param {string} dataString The SVG path data string. 1226 */ 1227 SvgWriter__prototype.S/*appendPath*/ = function appendPath (color, dataString) { 1228 this.F/*_s*/ += '<path fill="' + color + '" d="' + dataString + '"/>'; 1229 }; 1230 1231 /** 1232 * Gets the rendered image as an SVG string. 1233 */ 1234 SvgWriter__prototype.toString = function toString () { 1235 return this.F/*_s*/ + "</svg>"; 1236 }; 1237 1238 /** 1239 * Draws an identicon as an SVG string. 1240 * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. 1241 * @param {number} size - Icon size in pixels. 1242 * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any 1243 * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be 1244 * specified in place of a configuration object. 1245 * @returns {string} SVG string 1246 */ 1247 function toSvg(hashOrValue, size, config) { 1248 var writer = new SvgWriter(size); 1249 iconGenerator(new SvgRenderer(writer), 1250 isValidHash(hashOrValue) || computeHash(hashOrValue), 1251 config); 1252 return writer.toString(); 1253 } 1254 1255 /** 1256 * Creates a new element and adds it to the specified parent. 1257 * @param {Element} parentNode 1258 * @param {string} name 1259 * @param {...(string|number)} keyValuePairs 1260 */ 1261 function SvgElement_append(parentNode, name) { 1262 var keyValuePairs = [], len = arguments.length - 2; 1263 while ( len-- > 0 ) keyValuePairs[ len ] = arguments[ len + 2 ]; 1264 1265 var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name); 1266 1267 for (var i = 0; i + 1 < keyValuePairs.length; i += 2) { 1268 el.setAttribute( 1269 /** @type {string} */(keyValuePairs[i]), 1270 /** @type {string} */(keyValuePairs[i + 1]) 1271 ); 1272 } 1273 1274 parentNode.appendChild(el); 1275 } 1276 1277 1278 /** 1279 * Renderer producing SVG output. 1280 */ 1281 function SvgElement(element) { 1282 // Don't use the clientWidth and clientHeight properties on SVG elements 1283 // since Firefox won't serve a proper value of these properties on SVG 1284 // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811) 1285 // Instead use 100px as a hardcoded size (the svg viewBox will rescale 1286 // the icon to the correct dimensions) 1287 var iconSize = this.k/*iconSize*/ = Math.min( 1288 (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100), 1289 (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100) 1290 ); 1291 1292 /** 1293 * @type {Element} 1294 * @private 1295 */ 1296 this.W/*_el*/ = element; 1297 1298 // Clear current SVG child elements 1299 while (element.firstChild) { 1300 element.removeChild(element.firstChild); 1301 } 1302 1303 // Set viewBox attribute to ensure the svg scales nicely. 1304 element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize); 1305 element.setAttribute("preserveAspectRatio", "xMidYMid meet"); 1306 } 1307 var SvgElement__prototype = SvgElement.prototype; 1308 1309 /** 1310 * Fills the background with the specified color. 1311 * @param {string} fillColor Fill color on the format #rrggbb. 1312 * @param {number} opacity Opacity in the range [0.0, 1.0]. 1313 */ 1314 SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) { 1315 if (opacity) { 1316 SvgElement_append(this.W/*_el*/, "rect", 1317 SVG_CONSTANTS.U/*WIDTH*/, "100%", 1318 SVG_CONSTANTS.V/*HEIGHT*/, "100%", 1319 "fill", fillColor, 1320 "opacity", opacity); 1321 } 1322 }; 1323 1324 /** 1325 * Appends a path to the SVG element. 1326 * @param {string} color Fill color on format #xxxxxx. 1327 * @param {string} dataString The SVG path data string. 1328 */ 1329 SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) { 1330 SvgElement_append(this.W/*_el*/, "path", 1331 "fill", color, 1332 "d", dataString); 1333 }; 1334 1335 /** 1336 * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute. 1337 */ 1338 function updateAll() { 1339 if (documentQuerySelectorAll) { 1340 update(ICON_SELECTOR); 1341 } 1342 } 1343 1344 /** 1345 * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute that have not already 1346 * been rendered. 1347 */ 1348 function updateAllConditional() { 1349 if (documentQuerySelectorAll) { 1350 /** @type {NodeListOf<HTMLElement>} */ 1351 var elements = documentQuerySelectorAll(ICON_SELECTOR); 1352 1353 for (var i = 0; i < elements.length; i++) { 1354 var el = elements[i]; 1355 if (!el[IS_RENDERED_PROPERTY]) { 1356 update(el); 1357 } 1358 } 1359 } 1360 } 1361 1362 /** 1363 * Updates the identicon in the specified `<canvas>` or `<svg>` elements. 1364 * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type 1365 * `<svg>` or `<canvas>`, or a CSS selector to such an element. 1366 * @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or 1367 * `data-jdenticon-value` attribute will be evaluated. 1368 * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any 1369 * global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be 1370 * specified in place of a configuration object. 1371 */ 1372 function update(el, hashOrValue, config) { 1373 renderDomElement(el, hashOrValue, config, function (el, iconType) { 1374 if (iconType) { 1375 return iconType == ICON_TYPE_SVG ? 1376 new SvgRenderer(new SvgElement(el)) : 1377 new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d")); 1378 } 1379 }); 1380 } 1381 1382 /** 1383 * Updates the identicon in the specified canvas or svg elements. 1384 * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type 1385 * `<svg>` or `<canvas>`, or a CSS selector to such an element. 1386 * @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or 1387 * `data-jdenticon-value` attribute will be evaluated. 1388 * @param {Object|number|undefined} config 1389 * @param {function(Element,number):Renderer} rendererFactory - Factory function for creating an icon renderer. 1390 */ 1391 function renderDomElement(el, hashOrValue, config, rendererFactory) { 1392 if (typeof el === "string") { 1393 if (documentQuerySelectorAll) { 1394 var elements = documentQuerySelectorAll(el); 1395 for (var i = 0; i < elements.length; i++) { 1396 renderDomElement(elements[i], hashOrValue, config, rendererFactory); 1397 } 1398 } 1399 return; 1400 } 1401 1402 // Hash selection. The result from getValidHash or computeHash is 1403 // accepted as a valid hash. 1404 var hash = 1405 // 1. Explicit valid hash 1406 isValidHash(hashOrValue) || 1407 1408 // 2. Explicit value (`!= null` catches both null and undefined) 1409 hashOrValue != null && computeHash(hashOrValue) || 1410 1411 // 3. `data-jdenticon-hash` attribute 1412 isValidHash(el.getAttribute(ATTRIBUTES.t/*HASH*/)) || 1413 1414 // 4. `data-jdenticon-value` attribute. 1415 // We want to treat an empty attribute as an empty value. 1416 // Some browsers return empty string even if the attribute 1417 // is not specified, so use hasAttribute to determine if 1418 // the attribute is specified. 1419 el.hasAttribute(ATTRIBUTES.o/*VALUE*/) && computeHash(el.getAttribute(ATTRIBUTES.o/*VALUE*/)); 1420 1421 if (!hash) { 1422 // No hash specified. Don't render an icon. 1423 return; 1424 } 1425 1426 var renderer = rendererFactory(el, getIdenticonType(el)); 1427 if (renderer) { 1428 // Draw icon 1429 iconGenerator(renderer, hash, config); 1430 el[IS_RENDERED_PROPERTY] = true; 1431 } 1432 } 1433 1434 /** 1435 * Renders an identicon for all matching supported elements. 1436 * 1437 * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not 1438 * specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be 1439 * evaluated. 1440 * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global 1441 * configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be 1442 * specified in place of a configuration object. 1443 */ 1444 function jdenticonJqueryPlugin(hashOrValue, config) { 1445 this["each"](function (index, el) { 1446 update(el, hashOrValue, config); 1447 }); 1448 return this; 1449 } 1450 1451 // This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js 1452 1453 var jdenticon = updateAll; 1454 1455 defineConfigProperty(jdenticon); 1456 1457 // Export public API 1458 jdenticon["configure"] = configure; 1459 jdenticon["drawIcon"] = drawIcon; 1460 jdenticon["toSvg"] = toSvg; 1461 jdenticon["update"] = update; 1462 jdenticon["updateCanvas"] = update; 1463 jdenticon["updateSvg"] = update; 1464 1465 /** 1466 * Specifies the version of the Jdenticon package in use. 1467 * @type {string} 1468 */ 1469 jdenticon["version"] = "3.3.0"; 1470 1471 /** 1472 * Specifies which bundle of Jdenticon that is used. 1473 * @type {string} 1474 */ 1475 jdenticon["bundle"] = "browser-umd"; 1476 1477 // Basic jQuery plugin 1478 var jQuery = GLOBAL["jQuery"]; 1479 if (jQuery) { 1480 jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin; 1481 } 1482 1483 /** 1484 * This function is called once upon page load. 1485 */ 1486 function jdenticonStartup() { 1487 var replaceMode = ( 1488 jdenticon[CONFIG_PROPERTIES.n/*MODULE*/] || 1489 GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] || 1490 { } 1491 )["replaceMode"]; 1492 1493 if (replaceMode != "never") { 1494 updateAllConditional(); 1495 1496 if (replaceMode == "observe") { 1497 observer(update); 1498 } 1499 } 1500 } 1501 1502 // Schedule to render all identicons on the page once it has been loaded. 1503 whenDocumentIsReady(jdenticonStartup); 1504 1505 return jdenticon; 1506 1507 });