diff --git a/src/main/resources/jdenticon.js b/src/main/resources/jdenticon.js index 4a86bcb..dadd7f6 100644 --- a/src/main/resources/jdenticon.js +++ b/src/main/resources/jdenticon.js @@ -1,285 +1,815 @@ -// Jdenticon 1.3.2 | jdenticon.com | zlib licensed | (c) 2014-2015 Daniel Mester Pirttijärvi -(function (k, g, h) { - var l = h(k, k.jQuery); - "undefined" !== typeof module && "exports" in module ? module.exports = l : "function" === typeof define && define.amd ? define([], function () { - return l - }) : k[g] = l -})(this, "jdenticon", function (k, g) { - function h(b, a) { - this.x = b; - this.y = a - } +/** + * Jdenticon 1.4.0 + * http://jdenticon.com + * + * Built: 2016-12-10T17:10:50.251Z + * + * Copyright (c) 2014-2016 Daniel Mester Pirttijärvi + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + */ - function l(b, a, c, d) { - this.o = b; - this.s = a; - this.f = c; - this.l = d - } +/*jslint bitwise: true */ - function A(b) { - this.C = b; - this.m = l.O - } +(function (global, name, factory) { + var jQuery = global["jQuery"], + jdenticon = factory(global, jQuery); - function m(b) { - b |= 0; - return 0 > b ? "00" : 16 > b ? "0" + b.toString(16) : 256 > b ? b.toString(16) : "ff" + // Node.js + if (typeof module !== "undefined" && "exports" in module) { + module["exports"] = jdenticon; } - - function q(b, a, c) { - c = 0 > c ? c + 6 : 6 < c ? c - 6 : c; - return m(255 * (1 > c ? b + (a - b) * c : 3 > c ? a : 4 > c ? b + (a - - b) * (4 - c) : b)) + // RequireJS + else if (typeof define === "function" && define["amd"]) { + define([], function () { return jdenticon; }); } - - function D(b, a) { - return [n.w(0, 0, a.H(0)), n.v(b, a.A, a.u(.5)), n.w(0, 0, a.H(1)), n.v(b, a.A, a.u(1)), n.v(b, a.A, a.u(0))] + // No module loader + else { + global[name] = jdenticon; } +})(this, "jdenticon", function (global, jQuery) { + "use strict"; - function r(b, a, c, d, t) { - var f = 0, u = 0; - function v(c, d, t, e, g) { - e = e ? parseInt(a.charAt(e), 16) : 0; - d = d[parseInt(a.charAt(t), 16) % d.length]; - b.F(n[m[c]]); - for (c = 0; c < g.length; c++)k.m = new l(f + g[c][0] * h, u + g[c][1] * h, h, e++ % 4), d(k, h, c); - b.G() - } - function e(a) { - if (0 <= a.indexOf(g))for (var b = 0; b < a.length; b++)if (0 <= m.indexOf(a[b]))return !0 + + /** + * Represents a point. + * @private + * @constructor + */ + function Point(x, y) { + this.x = x; + this.y = y; + }; + + + /** + * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself, + * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly. + * @param {number} x The x-coordinate of the upper left corner of the transformed rectangle. + * @param {number} y The y-coordinate of the upper left corner of the transformed rectangle. + * @param {number} size The size of the transformed rectangle. + * @param {number} rotation Rotation specified as 0 = 0 rad, 1 = 0.5π rad, 2 = π rad, 3 = 1.5π rad + * @private + * @constructor + */ + function Transform(x, y, size, rotation) { + this._x = x; + this._y = y; + this._size = size; + this._rotation = rotation; + } + Transform.prototype = { + /** + * Transforms the specified point based on the translation and rotation specification for this Transform. + * @param {number} x x-coordinate + * @param {number} y y-coordinate + * @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. + * @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. + */ + transformPoint: function (x, y, w, h) { + var right = this._x + this._size, + bottom = this._y + this._size; + return this._rotation === 1 ? new Point(right - y - (h || 0), this._y + x) : + this._rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) : + this._rotation === 3 ? new Point(this._x + y, bottom - x - (w || 0)) : + new Point(this._x + x, this._y + y); } + }; + Transform.noTransform = new Transform(0, 0, 0, 0); + + + + /** + * Provides helper functions for rendering common basic shapes. + * @private + * @constructor + */ + function Graphics(renderer) { + this._renderer = renderer; + this._transform = Transform.noTransform; + } + Graphics.prototype = { + /** + * Adds a polygon to the underlying renderer. + * @param {Array} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ] + * @param {boolean=} invert Specifies if the polygon will be inverted. + */ + addPolygon: function (points, invert) { + var di = invert ? -2 : 2, + transform = this._transform, + transformedPoints = [], + i; + + for (i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) { + transformedPoints.push(transform.transformPoint(points[i], points[i + 1])); + } + + this._renderer.addPolygon(transformedPoints); + }, + + /** + * Adds a polygon to the underlying renderer. + * Source: http://stackoverflow.com/a/2173084 + * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse. + * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse. + * @param {number} size The size of the ellipse. + * @param {boolean=} invert Specifies if the ellipse will be inverted. + */ + addCircle: function (x, y, size, invert) { + var p = this._transform.transformPoint(x, y, size, size); + this._renderer.addCircle(p, size, invert); + }, + + /** + * Adds a rectangle to the underlying renderer. + * @param {number} x The x-coordinate of the upper left corner of the rectangle. + * @param {number} y The y-coordinate of the upper left corner of the rectangle. + * @param {number} w The width of the rectangle. + * @param {number} h The height of the rectangle. + * @param {boolean=} invert Specifies if the rectangle will be inverted. + */ + addRectangle: function (x, y, w, h, invert) { + this.addPolygon([ + x, y, + x + w, y, + x + w, y + h, + x, y + h + ], invert); + }, + + /** + * Adds a right triangle to the underlying renderer. + * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle. + * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle. + * @param {number} w The width of the triangle. + * @param {number} h The height of the triangle. + * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle. + * @param {boolean=} invert Specifies if the triangle will be inverted. + */ + addTriangle: function (x, y, w, h, r, invert) { + var points = [ + x + w, y, + x + w, y + h, + x, y + h, + x, y + ]; + points.splice(((r || 0) % 4) * 2, 2); + this.addPolygon(points, invert); + }, - d = c * (void 0 === d ? .08 : d) | 0; - c -= 2 * d; - if (30 > c)throw Error("Jdenticon cannot render identicons smaller than 30 pixels."); - if (!/^[0-9a-f]{11,}$/i.test(a))throw Error("Invalid hash passed to Jdenticon."); - var k = new A(b), h = 0 | c / 4, f = f + (0 | d + c / 2 - 2 * h), u = u + (0 | d + c / 2 - 2 * h), n = D(parseInt(a.substr(-7), 16) / 268435455, t), m = [], g; - for (c = 0; 3 > c; c++) { - g = parseInt(a.charAt(8 + c), 16) % n.length; - if (e([0, 4]) || e([2, 3]))g = 1; - m.push(g) + /** + * Adds a rhombus to the underlying renderer. + * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus. + * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus. + * @param {number} w The width of the rhombus. + * @param {number} h The height of the rhombus. + * @param {boolean=} invert Specifies if the rhombus will be inverted. + */ + addRhombus: function (x, y, w, h, invert) { + this.addPolygon([ + x + w / 2, y, + x + w, y + h / 2, + x + w / 2, y + h, + x, y + h / 2 + ], invert); } - v(0, w.J, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]); - v(1, w.J, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]); - v(2, w.N, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]) - } + }; + + + + + var shapes = { + center: [ + /** @param {Graphics} g */ + function (g, cell, index) { + var k = cell * 0.42; + g.addPolygon([ + 0, 0, + cell, 0, + cell, cell - k * 2, + cell - k, cell, + 0, cell + ]); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var w = 0 | (cell * 0.5), + h = 0 | (cell * 0.8); + g.addTriangle(cell - w, 0, w, h, 2); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var s = 0 | (cell / 3); + g.addRectangle(s, s, cell - s, cell - s); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var inner = cell * 0.1, + inner = + inner > 1 ? (0 | inner) : // large icon => truncate decimals + inner > 0.5 ? 1 : // medium size icon => fixed width + inner, // small icon => anti-aliased border + + // Use fixed outer border widths in small icons to ensure the border is drawn + outer = + cell < 6 ? 1 : + cell < 8 ? 2 : + (0 | (cell * 0.25)); + + g.addRectangle(outer, outer, cell - inner - outer, cell - inner - outer); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var m = 0 | (cell * 0.15), + s = 0 | (cell * 0.5); + g.addCircle(cell - s - m, cell - s - m, s); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var inner = cell * 0.1, + outer = inner * 4; - function B() { - this.i = "" - } + g.addRectangle(0, 0, cell, cell); + g.addPolygon([ + outer, outer, + cell - inner, outer, + outer + (cell - outer - inner) / 2, cell - inner + ], true); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + g.addPolygon([ + 0, 0, + cell, 0, + cell, cell * 0.7, + cell * 0.4, cell * 0.4, + cell * 0.7, cell, + 0, cell + ]); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + g.addTriangle(cell / 2, cell / 2, cell / 2, cell / 2, 3); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + g.addRectangle(0, 0, cell, cell / 2); + g.addRectangle(0, cell / 2, cell / 2, cell / 2); + g.addTriangle(cell / 2, cell / 2, cell / 2, cell / 2, 1); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var inner = cell * 0.14, + inner = + cell < 8 ? inner : // small icon => anti-aliased border + (0 | inner), // large icon => truncate decimals + + // Use fixed outer border widths in small icons to ensure the border is drawn + outer = + cell < 4 ? 1 : + cell < 6 ? 2 : + (0 | (cell * 0.35)); + + g.addRectangle(0, 0, cell, cell); + g.addRectangle(outer, outer, cell - outer - inner, cell - outer - inner, true); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var inner = cell * 0.12, + outer = inner * 3; - function x(b, a) { - this.j = {}; - this.f = {M: b, I: a} - } + g.addRectangle(0, 0, cell, cell); + g.addCircle(outer, outer, cell - inner - outer, true); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + g.addTriangle(cell / 2, cell / 2, cell / 2, cell / 2, 3); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var m = cell * 0.25; + g.addRectangle(0, 0, cell, cell); + g.addRhombus(m, m, cell - m, cell - m, true); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var m = cell * 0.4, s = cell * 1.2; + if (!index) { + g.addCircle(m, m, s); + } + } + ], + + outer: [ + /** @param {Graphics} g */ + function (g, cell, index) { + g.addTriangle(0, 0, cell, cell, 0); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + g.addTriangle(0, cell / 2, cell, cell / 2, 0); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + g.addRhombus(0, 0, cell, cell); + }, + /** @param {Graphics} g */ + function (g, cell, index) { + var m = cell / 6; + g.addCircle(m, m, cell - 2 * m); + } + ] + }; - function y(b, - a, c) { - this.h = b; - b.clearRect(0, 0, a, c) + + + + function decToHex(v) { + v |= 0; // Ensure integer value + return v < 0 ? "00" : + v < 16 ? "0" + v.toString(16) : + v < 256 ? v.toString(16) : + "ff"; } - - function z() { - function b(a, b, f) { - var e = c[a] instanceof Array ? c[a] : [b, f]; - return function (a) { - a = e[0] + a * (e[1] - e[0]); - return 0 > a ? 0 : 1 < a ? 1 : a + + function hueToRgb(m1, m2, h) { + h = h < 0 ? h + 6 : h > 6 ? h - 6 : h; + return decToHex(255 * ( + h < 1 ? m1 + (m2 - m1) * h : + h < 3 ? m2 : + h < 4 ? m1 + (m2 - m1) * (4 - h) : + m1)); + } + + /** + * Functions for converting colors to hex-rgb representations. + * @private + */ + var color = { + /** + * @param {number} r Red channel [0, 255] + * @param {number} g Green channel [0, 255] + * @param {number} b Blue channel [0, 255] + */ + rgb: function (r, g, b) { + return "#" + decToHex(r) + decToHex(g) + decToHex(b); + }, + /** + * @param h Hue [0, 1] + * @param s Saturation [0, 1] + * @param l Lightness [0, 1] + */ + hsl: function (h, s, l) { + // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color + if (s == 0) { + var partialHex = decToHex(l * 255); + return "#" + partialHex + partialHex + partialHex; + } + else { + var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s, + m1 = l * 2 - m2; + return "#" + + hueToRgb(m1, m2, h * 6 + 2) + + hueToRgb(m1, m2, h * 6) + + hueToRgb(m1, m2, h * 6 - 2); } + }, + // This function will correct the lightness for the "dark" hues + correctedHsl: function (h, s, l) { + // The corrector specifies the perceived middle lightnesses for each hue + var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ], + corrector = correctors[(h * 6 + 0.5) | 0]; + + // Adjust the input lightness relative to the corrector + l = l < 0.5 ? l * corrector * 2 : corrector + (l - 0.5) * (1 - corrector) * 2; + + return color.hsl(h, s, l); } + }; - var a = e.config || k.jdenticon_config || {}, c = a.lightness || {}, a = a.saturation; - return {A: "number" == typeof a ? a : .5, u: b("color", .4, .8), H: b("grayscale", .3, .9)} + + + + /** + * Gets a set of identicon color candidates for a specified hue and config. + */ + function colorTheme(hue, config) { + return [ + // Dark gray + color.hsl(0, 0, config.grayscaleLightness(0)), + // Mid color + color.correctedHsl(hue, config.saturation, config.colorLightness(0.5)), + // Light gray + color.hsl(0, 0, config.grayscaleLightness(1)), + // Light color + color.correctedHsl(hue, config.saturation, config.colorLightness(1)), + // Dark color + color.correctedHsl(hue, config.saturation, config.colorLightness(0)) + ]; } - function p(b, a, c) { - if ("string" === typeof b) { - if (C) { - b = document.querySelectorAll(b); - for (var d = 0; d < b.length; d++)p(b[d], a, c) + + + + /** + * Draws an identicon to a specified renderer. + */ + function iconGenerator(renderer, hash, x, y, size, padding, config) { + var undefined; + + // Calculate padding + padding = (size * (padding === undefined ? 0.08 : padding)) | 0; + size -= padding * 2; + + if (!/^[0-9a-f]{11,}$/i.test(hash)) { + throw new Error("Invalid hash passed to Jdenticon."); + } + + var graphics = new Graphics(renderer); + + // Calculate cell size and ensure it is an integer + var cell = 0 | (size / 4); + + // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon + x += 0 | (padding + size / 2 - cell * 2); + y += 0 | (padding + size / 2 - cell * 2); + + function renderShape(colorIndex, shapes, index, rotationIndex, positions) { + var r = rotationIndex ? parseInt(hash.charAt(rotationIndex), 16) : 0, + shape = shapes[parseInt(hash.charAt(index), 16) % shapes.length], + i; + + renderer.beginShape(availableColors[selectedColorIndexes[colorIndex]]); + + for (i = 0; i < positions.length; i++) { + graphics._transform = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4); + shape(graphics, cell, i); } - } else if (b && b.tagName && (a = a || b.getAttribute("data-jdenticon-hash"))) { - var e = - "svg" == b.tagName.toLowerCase(), d = "canvas" == b.tagName.toLowerCase(); - if (e || d && "getContext" in b) { - var d = Number(b.getAttribute("width")) || b.clientWidth || 0, f = Number(b.getAttribute("height")) || b.clientHeight || 0, g = e ? new x(d, f) : new y(b.getContext("2d"), d, f); - r(g, a, Math.min(d, f), c, z()); - if (e) { - a = document.createElement("span"); - for (a.innerHTML = g.K(!1); b.firstChild;)b.removeChild(b.firstChild); - for (a = a.firstChild.childNodes; a.length;)b.appendChild(a[0]); - b.setAttribute("viewBox", "0 0 " + d + " " + f) + + renderer.endShape(); + } + + // AVAILABLE COLORS + var hue = parseInt(hash.substr(-7), 16) / 0xfffffff, + + // Available colors for this icon + availableColors = colorTheme(hue, config), + + // The index of the selected colors + selectedColorIndexes = [], + index; + + function isDuplicate(values) { + if (values.indexOf(index) >= 0) { + for (var i = 0; i < values.length; i++) { + if (selectedColorIndexes.indexOf(values[i]) >= 0) { + return true; + } } } } - } - - function e() { - C && - p("svg[data-jdenticon-hash],canvas[data-jdenticon-hash]") - } - l.prototype = { - L: function (b, a, c, d) { - var e = this.o + this.f, f = this.s + this.f; - return 1 === this.l ? new h(e - a - (d || 0), this.s + b) : 2 === this.l ? new h(e - b - (c || 0), f - a - (d || 0)) : 3 === this.l ? new h(this.o + a, f - b - (c || 0)) : new h(this.o + b, this.s + a) + for (var i = 0; i < 3; i++) { + index = parseInt(hash.charAt(8 + i), 16) % availableColors.length; + if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo + isDuplicate([2, 3])) { // Disallow light gray and light color combo + index = 1; + } + selectedColorIndexes.push(index); } + + // ACTUAL RENDERING + // Sides + renderShape(0, shapes.outer, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]); + // Corners + renderShape(1, shapes.outer, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]); + // Center + renderShape(2, shapes.center, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]); }; - l.O = new l(0, 0, 0, 0); - A.prototype = { - a: function (b, a) { - var c = a ? -2 : 2, d = this.m, e = [], f; - for (f = a ? b.length - 2 : 0; f < b.length && 0 <= f; f += c)e.push(d.L(b[f], b[f + 1])); - this.C.a(e) - }, b: function (b, a, c, d) { - this.C.b(this.m.L(b, a, c, c), c, d) - }, c: function (b, - a, c, d, e) { - this.a([b, a, b + c, a, b + c, a + d, b, a + d], e) - }, g: function (b, a, c, d, e, f) { - b = [b + c, a, b + c, a + d, b, a + d, b, a]; - b.splice((e || 0) % 4 * 2, 2); - this.a(b, f) - }, D: function (b, a, c, d, e) { - this.a([b + c / 2, a, b + c, a + d / 2, b + c / 2, a + d, b, a + d / 2], e) + + + + /** + * Represents an SVG path element. + * @private + * @constructor + */ + function SvgPath() { + /** + * This property holds the data string (path.d) of the SVG path. + */ + this.dataString = ""; + } + SvgPath.prototype = { + /** + * Adds a polygon with the current fill color to the SVG path. + * @param points An array of Point objects. + */ + addPolygon: function (points) { + var dataString = "M" + points[0].x + " " + points[0].y; + for (var i = 1; i < points.length; i++) { + dataString += "L" + points[i].x + " " + points[i].y; + } + this.dataString += dataString + "Z"; + }, + /** + * Adds a circle with the current fill color to the SVG path. + * @param {Point} point The upper left corner of the circle bounding box. + * @param {number} diameter The diameter of the circle. + * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). + */ + addCircle: function (point, diameter, counterClockwise) { + var sweepFlag = counterClockwise ? 0 : 1, + radius = diameter / 2; + this.dataString += + "M" + (point.x) + " " + (point.y + radius) + + "a" + radius + "," + radius + " 0 1," + sweepFlag + " " + diameter + ",0" + + "a" + radius + "," + radius + " 0 1," + sweepFlag + " " + (-diameter) + ",0"; } }; - var w = { - N: [function (b, a) { - var c = .42 * a; - b.a([0, 0, a, 0, a, a - 2 * c, a - c, a, 0, a]) - }, function (b, a) { - var c = 0 | .5 * a; - b.g(a - c, 0, c, 0 | .8 * a, 2) - }, function (b, a) { - var c = 0 | a / 3; - b.c(c, c, a - c, a - c) - }, function (b, a) { - var c = 0 | .1 * a, d = 0 | .25 * a; - b.c(d, d, a - c - d, a - c - d) - }, function (b, a) { - var c = 0 | .15 * a, d = 0 | .5 * a; - b.b(a - d - c, a - d - c, d) - }, function (b, a) { - var c = - .1 * a, d = 4 * c; - b.c(0, 0, a, a); - b.a([d, d, a - c, d, d + (a - d - c) / 2, a - c], !0) - }, function (b, a) { - b.a([0, 0, a, 0, a, .7 * a, .4 * a, .4 * a, .7 * a, a, 0, a]) - }, function (b, a) { - b.g(a / 2, a / 2, a / 2, a / 2, 3) - }, function (b, a) { - b.c(0, 0, a, a / 2); - b.c(0, a / 2, a / 2, a / 2); - b.g(a / 2, a / 2, a / 2, a / 2, 1) - }, function (b, a) { - var c = 0 | .14 * a, d = 0 | .35 * a; - b.c(0, 0, a, a); - b.c(d, d, a - d - c, a - d - c, !0) - }, function (b, a) { - var c = .12 * a, d = 3 * c; - b.c(0, 0, a, a); - b.b(d, d, a - c - d, !0) - }, function (b, a) { - b.g(a / 2, a / 2, a / 2, a / 2, 3) - }, function (b, a) { - var c = .25 * a; - b.c(0, 0, a, a); - b.D(c, c, a - c, a - c, !0) - }, function (b, a, c) { - var d = .4 * a; - c || b.b(d, - d, 1.2 * a) - }], J: [function (b, a) { - b.g(0, 0, a, a, 0) - }, function (b, a) { - b.g(0, a / 2, a, a / 2, 0) - }, function (b, a) { - b.D(0, 0, a, a) - }, function (b, a) { - var c = a / 6; - b.b(c, c, a - 2 * c) - }] - }, n = { - P: function (b, a, c) { - return "#" + m(b) + m(a) + m(c) - }, w: function (b, a, c) { - if (0 == a)return b = m(255 * c), "#" + b + b + b; - a = .5 >= c ? c * (a + 1) : c + a - c * a; - c = 2 * c - a; - return "#" + q(c, a, 6 * b + 2) + q(c, a, 6 * b) + q(c, a, 6 * b - 2) - }, v: function (b, a, c) { - var d = [.55, .5, .5, .46, .6, .55, .55][6 * b + .5 | 0]; - return n.w(b, a, .5 > c ? c * d * 2 : d + (c - .5) * (1 - d) * 2) + + + + /** + * Renderer producing SVG output. + * @private + * @constructor + */ + function SvgRenderer(width, height) { + this._pathsByColor = { }; + this._size = { w: width, h: height }; + } + SvgRenderer.prototype = { + /** + * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. + * @param {string} color Fill color on format #xxxxxx. + */ + beginShape: function (color) { + this._path = this._pathsByColor[color] || (this._pathsByColor[color] = new SvgPath()); + }, + /** + * Marks the end of the currently drawn shape. + */ + endShape: function () { }, + /** + * Adds a polygon with the current fill color to the SVG. + * @param points An array of Point objects. + */ + addPolygon: function (points) { + this._path.addPolygon(points); + }, + /** + * Adds a circle with the current fill color to the SVG. + * @param {Point} point The upper left corner of the circle bounding box. + * @param {number} diameter The diameter of the circle. + * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). + */ + addCircle: function (point, diameter, counterClockwise) { + this._path.addCircle(point, diameter, counterClockwise); + }, + /** + * Gets the rendered image as an SVG string. + * @param {boolean=} fragment If true, the container svg element is not included in the result. + */ + toSvg: function (fragment) { + var svg = fragment ? '' : + ''; + + for (var color in this._pathsByColor) { + svg += ''; + } + + return fragment ? svg : + svg + ''; } }; - B.prototype = { - a: function (b) { - for (var a = "M" + b[0].x + " " + b[0].y, c = 1; c < - b.length; c++)a += "L" + b[c].x + " " + b[c].y; - this.i += a + "Z" - }, b: function (b, a, c) { - c = c ? 0 : 1; - var d = a / 2; - this.i += "M" + b.x + " " + (b.y + d) + "a" + d + "," + d + " 0 1," + c + " " + a + ",0a" + d + "," + d + " 0 1," + c + " " + -a + ",0" + + + + /** + * Renderer redirecting drawing commands to a canvas context. + * @private + * @constructor + */ + function CanvasRenderer(ctx, width, height) { + this._ctx = ctx; + ctx.clearRect(0, 0, width, height); + } + CanvasRenderer.prototype = { + /** + * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. + * @param {string} color Fill color on format #xxxxxx. + */ + beginShape: function (color) { + this._ctx.fillStyle = color; + this._ctx.beginPath(); + }, + /** + * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas. + */ + endShape: function () { + this._ctx.fill(); + }, + /** + * Adds a polygon to the rendering queue. + * @param points An array of Point objects. + */ + addPolygon: function (points) { + var ctx = this._ctx, i; + ctx.moveTo(points[0].x, points[0].y); + for (i = 1; i < points.length; i++) { + ctx.lineTo(points[i].x, points[i].y); + } + ctx.closePath(); + }, + /** + * Adds a circle to the rendering queue. + * @param {Point} point The upper left corner of the circle bounding box. + * @param {number} diameter The diameter of the circle. + * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). + */ + addCircle: function (point, diameter, counterClockwise) { + var ctx = this._ctx, + radius = diameter / 2; + ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise); + ctx.closePath(); } }; - x.prototype = { - F: function (b) { - this.B = this.j[b] || (this.j[b] = new B) - }, G: function () { - }, a: function (b) { - this.B.a(b) - }, b: function (b, a, c) { - this.B.b(b, a, c) - }, K: function (b) { - var a = b ? "" : '', - c; - for (c in this.j)a += ''; - return b ? a : a + "" + + + + + + + var /** @const */ + HASH_ATTRIBUTE = "data-jdenticon-hash", + supportsQuerySelectorAll = "document" in global && "querySelectorAll" in document; + + /** + * Gets the normalized current Jdenticon color configuration. Missing fields have default values. + */ + function getCurrentConfig() { + var configObject = jdenticon["config"] || global["jdenticon_config"] || { }, + lightnessConfig = configObject["lightness"] || { }, + saturation = configObject["saturation"]; + + /** + * Creates a lightness range. + */ + function lightness(configName, defaultMin, defaultMax) { + var range = lightnessConfig[configName] instanceof Array ? lightnessConfig[configName] : [defaultMin, defaultMax]; + + /** + * Gets a lightness relative the specified value in the specified lightness range. + */ + return function (value) { + value = range[0] + value * (range[1] - range[0]); + return value < 0 ? 0 : value > 1 ? 1 : value; + }; } - }; - y.prototype = { - F: function (b) { - this.h.fillStyle = b; - this.h.beginPath() - }, G: function () { - this.h.fill() - }, a: function (b) { - var a = this.h, c; - a.moveTo(b[0].x, b[0].y); - for (c = 1; c < b.length; c++)a.lineTo(b[c].x, b[c].y); - a.closePath() - }, b: function (b, a, c) { - var d = this.h; - a /= 2; - d.arc(b.x + a, b.y + a, a, 0, 2 * Math.PI, c); - d.closePath() + + return { + saturation: typeof saturation == "number" ? saturation : 0.5, + colorLightness: lightness("color", 0.4, 0.8), + grayscaleLightness: lightness("grayscale", 0.3, 0.9) } - }; - var C = "document" in k && "querySelectorAll" in document; - e.drawIcon = function (b, a, c) { - if (!b)throw Error("No canvas specified."); - b = new y(b, c, c); - r(b, a, c, 0, z()) - }; - e.toSvg = function (b, a, c) { - var d = new x(a, a); - r(d, b, a, c, z()); - return d.K() - }; - e.update = p; - e.version = "1.3.2"; - g && (g.fn.jdenticon = function (b, a) { - this.each(function (c, d) { - p(d, b, a) - }); - return this - }); - "function" === typeof setTimeout && setTimeout(e, 0); - return e + } + + /** + * Updates the identicon in the specified canvas or svg elements. + * @param {string=} hash Optional hash to be rendered. If not specified, the hash specified by the data-jdenticon-hash is used. + * @param {number=} padding Optional padding in percents. Extra padding might be added to center the rendered identicon. + */ + function update(el, hash, padding) { + if (typeof(el) === "string") { + if (supportsQuerySelectorAll) { + var elements = document.querySelectorAll(el); + for (var i = 0; i < elements.length; i++) { + update(elements[i], hash, padding); + } + } + return; + } + if (!el || !el["tagName"]) { + // No element found + return; + } + hash = hash || el.getAttribute(HASH_ATTRIBUTE); + if (!hash) { + // No hash specified + return; + } + + var isSvg = el["tagName"].toLowerCase() == "svg", + isCanvas = el["tagName"].toLowerCase() == "canvas"; + + // Ensure we have a supported element + if (!isSvg && !(isCanvas && "getContext" in el)) { + return; + } + + var width = Number(el.getAttribute("width")) || el.clientWidth || 0, + height = Number(el.getAttribute("height")) || el.clientHeight || 0, + renderer = isSvg ? new SvgRenderer(width, height) : new CanvasRenderer(el.getContext("2d"), width, height), + size = Math.min(width, height); + + // Draw icon + iconGenerator(renderer, hash, 0, 0, size, padding, getCurrentConfig()); + + // SVG needs postprocessing + if (isSvg) { + // Parse svg to a temporary span element. + // Simply using innerHTML does unfortunately not work on IE. + var wrapper = document.createElement("span"); + wrapper.innerHTML = renderer.toSvg(false); + + // Then replace the content of the target element with the parsed svg. + while (el.firstChild) { + el.removeChild(el.firstChild); + } + var newNodes = wrapper.firstChild.childNodes; + while (newNodes.length) { + el.appendChild(newNodes[0]); + } + + // Set viewBox attribute to ensure the svg scales nicely. + el.setAttribute("viewBox", "0 0 " + width + " " + height); + } + } + + /** + * Draws an identicon to a context. + */ + function drawIcon(ctx, hash, size) { + if (!ctx) { + throw new Error("No canvas specified."); + } + + var renderer = new CanvasRenderer(ctx, size, size); + iconGenerator(renderer, hash, 0, 0, size, 0, getCurrentConfig()); + } + + /** + * Draws an identicon to a context. + * @param {number=} padding Optional padding in percents. Extra padding might be added to center the rendered identicon. + */ + function toSvg(hash, size, padding) { + var renderer = new SvgRenderer(size, size); + iconGenerator(renderer, hash, 0, 0, size, padding, getCurrentConfig()); + return renderer.toSvg(); + } + + /** + * Updates all canvas elements with the data-jdenticon-hash attribute. + */ + function jdenticon() { + if (supportsQuerySelectorAll) { + update("svg[" + HASH_ATTRIBUTE + "],canvas[" + HASH_ATTRIBUTE + "]"); + } + } + + // Public API + jdenticon["drawIcon"] = drawIcon; + jdenticon["toSvg"] = toSvg; + jdenticon["update"] = update; + jdenticon["version"] = "1.4.0"; + + // Basic jQuery plugin + if (jQuery) { + jQuery["fn"]["jdenticon"] = function (hash, padding) { + this["each"](function (index, el) { + update(el, hash, padding); + }); + return this; + }; + } + + // Schedule to render all identicons on the page once it has been loaded. + if (typeof setTimeout === "function") { + setTimeout(jdenticon, 0); + } + + return jdenticon; + });