').prependTo( $this );
}
caption.html( oSettings.caption );
@@ -436,6 +437,14 @@
oSettings.captionNode = caption[0];
}
+ // Place the colgroup element in the correct location for the HTML structure
+ if (caption.length) {
+ oSettings.colgroup.insertAfter(caption);
+ }
+ else {
+ oSettings.colgroup.prependTo(oSettings.nTable);
+ }
+
if ( thead.length === 0 ) {
thead = $('
').appendTo($this);
}
@@ -451,7 +460,7 @@
if ( tfoot.length === 0 ) {
// If we are a scrolling table, and no footer has been given, then we need to create
// a tfoot element for the caption element to be appended to
- tfoot = $('').appendTo($this);
+ tfoot = $('').insertAfter(thead);
}
oSettings.nTFoot = tfoot[0];
@@ -516,7 +525,7 @@
*
* @type string
*/
- builder: "bs5/dt-2.3.5",
+ builder: "bs5/dt-2.3.6",
/**
* Buttons. For use with the Buttons extension for DataTables. This is
@@ -1292,7 +1301,7 @@
};
// Replaceable function in api.util
- var _stripHtml = function (input) {
+ var _stripHtml = function (input, replacement) {
if (! input || typeof input !== 'string') {
return input;
}
@@ -1304,7 +1313,7 @@
var previous;
- input = input.replace(_re_html, ''); // Complete tags
+ input = input.replace(_re_html, replacement || ''); // Complete tags
// Safety for incomplete script tag - use do / while to ensure that
// we get all instances
@@ -1769,7 +1778,7 @@
}
},
- stripHtml: function (mixed) {
+ stripHtml: function (mixed, replacement) {
var type = typeof mixed;
if (type === 'function') {
@@ -1777,7 +1786,7 @@
return;
}
else if (type === 'string') {
- return _stripHtml(mixed);
+ return _stripHtml(mixed, replacement);
}
return mixed;
},
@@ -3379,7 +3388,7 @@
colspan++;
}
- var titleSpan = $('span.dt-column-title', cell);
+ var titleSpan = $('.dt-column-title', cell);
structure[row][column] = {
cell: cell,
@@ -4093,8 +4102,8 @@
}
// Wrap the column title so we can write to it in future
- if ( $('span.dt-column-title', cell).length === 0) {
- $('')
+ if ( $('.dt-column-title', cell).length === 0) {
+ $(document.createElement(settings.columnTitleTag))
.addClass('dt-column-title')
.append(cell.childNodes)
.appendTo(cell);
@@ -4105,9 +4114,9 @@
isHeader &&
jqCell.filter(':not([data-dt-order=disable])').length !== 0 &&
jqCell.parent(':not([data-dt-order=disable])').length !== 0 &&
- $('span.dt-column-order', cell).length === 0
+ $('.dt-column-order', cell).length === 0
) {
- $('')
+ $(document.createElement(settings.columnTitleTag))
.addClass('dt-column-order')
.appendTo(cell);
}
@@ -4116,7 +4125,7 @@
// layout for those elements
var headerFooter = isHeader ? 'header' : 'footer';
- if ( $('span.dt-column-' + headerFooter, cell).length === 0) {
+ if ( $('div.dt-column-' + headerFooter, cell).length === 0) {
$('
')
.addClass('dt-column-' + headerFooter)
.append(cell.childNodes)
@@ -4273,6 +4282,10 @@
// Custom Ajax option to submit the parameters as a JSON string
if (baseAjax.submitAs === 'json' && typeof data === 'object') {
baseAjax.data = JSON.stringify(data);
+
+ if (!baseAjax.contentType) {
+ baseAjax.contentType = 'application/json; charset=utf-8';
+ }
}
if (typeof ajax === 'function') {
@@ -5531,7 +5544,7 @@
var autoClass = _ext.type.className[column.sType];
var padding = column.sContentPadding || (scrollX ? '-' : '');
var text = longest + padding;
- var insert = longest.indexOf('<') === -1
+ var insert = longest.indexOf('<') === -1 && longest.indexOf('&') === -1
? document.createTextNode(text)
: text
@@ -5719,15 +5732,20 @@
.replace(/id=".*?"/g, '')
.replace(/name=".*?"/g, '');
- var s = _stripHtml(cellString)
+ // Don't want Javascript at all in these calculation cells.
+ cellString = cellString.replace(//gi, ' ');
+
+ var noHtml = _stripHtml(cellString, ' ')
.replace( / /g, ' ' );
+ // The length is calculated on the text only, but we keep the HTML
+ // in the string so it can be used in the calculation table
collection.push({
- str: s,
- len: s.length
+ str: cellString,
+ len: noHtml.length
});
- allStrings.push(s);
+ allStrings.push(noHtml);
}
// Order and then cut down to the size we need
@@ -8782,7 +8800,7 @@
// Automatic - find the _last_ unique cell from the top that is not empty (last for
// backwards compatibility)
for (var i=0 ; i 6 ? h - 6 : h;
- return decToHex(255 * (
- h < 1 ? m1 + (m2 - m1) * h :
- h < 3 ? m2 :
- h < 4 ? m1 + (m2 - m1) * (4 - h) :
- m1));
-}
-
-/**
- * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported.
- * @returns {string}
- */
-function parseColor(color) {
- if (/^#[0-9a-f]{3,8}$/i.test(color)) {
- var result;
- var colorLength = color.length;
-
- if (colorLength < 6) {
- var r = color[1],
- g = color[2],
- b = color[3],
- a = color[4] || "";
- result = "#" + r + r + g + g + b + b + a + a;
- }
- if (colorLength == 7 || colorLength > 8) {
- result = color;
- }
-
- return result;
- }
-}
-
-/**
- * Converts a hexadecimal color to a CSS3 compatible color.
- * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA"
- * @returns {string}
- */
-function toCss3Color(hexColor) {
- var a = parseHex(hexColor, 7, 2);
- var result;
-
- if (isNaN(a)) {
- result = hexColor;
- } else {
- var r = parseHex(hexColor, 1, 2),
- g = parseHex(hexColor, 3, 2),
- b = parseHex(hexColor, 5, 2);
- result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")";
- }
-
- return result;
-}
-
-/**
- * Converts an HSL color to a hexadecimal RGB color.
- * @param {number} hue Hue in range [0, 1]
- * @param {number} saturation Saturation in range [0, 1]
- * @param {number} lightness Lightness in range [0, 1]
- * @returns {string}
- */
-function hsl(hue, saturation, lightness) {
- // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
- var result;
-
- if (saturation == 0) {
- var partialHex = decToHex(lightness * 255);
- result = partialHex + partialHex + partialHex;
- }
- else {
- var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation,
- m1 = lightness * 2 - m2;
- result =
- hueToRgb(m1, m2, hue * 6 + 2) +
- hueToRgb(m1, m2, hue * 6) +
- hueToRgb(m1, m2, hue * 6 - 2);
- }
-
- return "#" + result;
-}
-
-/**
- * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues
- * @param {number} hue Hue in range [0, 1]
- * @param {number} saturation Saturation in range [0, 1]
- * @param {number} lightness Lightness in range [0, 1]
- * @returns {string}
- */
-function correctedHsl(hue, saturation, lightness) {
- // The corrector specifies the perceived middle lightness for each hue
- var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ],
- corrector = correctors[(hue * 6 + 0.5) | 0];
-
- // Adjust the input lightness relative to the corrector
- lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2;
-
- return hsl(hue, saturation, lightness);
+function decToHex(v) {
+ v |= 0; // Ensure integer value
+ return v < 0 ? "00" :
+ v < 16 ? "0" + v.toString(16) :
+ v < 256 ? v.toString(16) :
+ "ff";
}
-/* global umdGlobal */
-
-// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
-// backward compatibility.
+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));
+}
+
+/**
+ * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported.
+ * @returns {string}
+ */
+function parseColor(color) {
+ if (/^#[0-9a-f]{3,8}$/i.test(color)) {
+ var result;
+ var colorLength = color.length;
+
+ if (colorLength < 6) {
+ var r = color[1],
+ g = color[2],
+ b = color[3],
+ a = color[4] || "";
+ result = "#" + r + r + g + g + b + b + a + a;
+ }
+ if (colorLength == 7 || colorLength > 8) {
+ result = color;
+ }
+
+ return result;
+ }
+}
+
+/**
+ * Converts a hexadecimal color to a CSS3 compatible color.
+ * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA"
+ * @returns {string}
+ */
+function toCss3Color(hexColor) {
+ var a = parseHex(hexColor, 7, 2);
+ var result;
+
+ if (isNaN(a)) {
+ result = hexColor;
+ } else {
+ var r = parseHex(hexColor, 1, 2),
+ g = parseHex(hexColor, 3, 2),
+ b = parseHex(hexColor, 5, 2);
+ result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")";
+ }
+
+ return result;
+}
+
+/**
+ * Converts an HSL color to a hexadecimal RGB color.
+ * @param {number} hue Hue in range [0, 1]
+ * @param {number} saturation Saturation in range [0, 1]
+ * @param {number} lightness Lightness in range [0, 1]
+ * @returns {string}
+ */
+function hsl(hue, saturation, lightness) {
+ // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
+ var result;
+
+ if (saturation == 0) {
+ var partialHex = decToHex(lightness * 255);
+ result = partialHex + partialHex + partialHex;
+ }
+ else {
+ var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation,
+ m1 = lightness * 2 - m2;
+ result =
+ hueToRgb(m1, m2, hue * 6 + 2) +
+ hueToRgb(m1, m2, hue * 6) +
+ hueToRgb(m1, m2, hue * 6 - 2);
+ }
+
+ return "#" + result;
+}
+
+/**
+ * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues
+ * @param {number} hue Hue in range [0, 1]
+ * @param {number} saturation Saturation in range [0, 1]
+ * @param {number} lightness Lightness in range [0, 1]
+ * @returns {string}
+ */
+function correctedHsl(hue, saturation, lightness) {
+ // The corrector specifies the perceived middle lightness for each hue
+ var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ],
+ corrector = correctors[(hue * 6 + 0.5) | 0];
+
+ // Adjust the input lightness relative to the corrector
+ lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2;
+
+ return hsl(hue, saturation, lightness);
+}
+
+/* global umdGlobal */
+
+// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
+// backward compatibility.
var GLOBAL = umdGlobal;
-/**
- * @typedef {Object} ParsedConfiguration
- * @property {number} colorSaturation
- * @property {number} grayscaleSaturation
- * @property {string} backColor
- * @property {number} iconPadding
- * @property {function(number):number} hue
- * @property {function(number):number} colorLightness
- * @property {function(number):number} grayscaleLightness
- */
-
-var CONFIG_PROPERTIES = {
- G/*GLOBAL*/: "jdenticon_config",
- n/*MODULE*/: "config",
-};
-
-var rootConfigurationHolder = {};
-
-/**
- * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console
- * when it is being used.
- * @param {!Object} rootObject
- */
-function defineConfigProperty(rootObject) {
- rootConfigurationHolder = rootObject;
-}
-
-/**
- * Sets a new icon style configuration. The new configuration is not merged with the previous one. *
- * @param {Object} newConfiguration - New configuration object.
- */
-function configure(newConfiguration) {
- if (arguments.length) {
- rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration;
- }
- return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/];
-}
-
-/**
- * Gets the normalized current Jdenticon color configuration. Missing fields have default values.
- * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A
- * local configuration overrides the global configuration in it entirety. This parameter can for backward
- * compatibility also contain a padding value. A padding value only overrides the global padding, not the
- * entire global configuration.
- * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor
- * explicitly to the API method.
- * @returns {ParsedConfiguration}
- */
-function getConfiguration(paddingOrLocalConfig, defaultPadding) {
- var configObject =
- typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig ||
- rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] ||
- GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
- { },
-
- lightnessConfig = configObject["lightness"] || { },
-
- // In versions < 2.1.0 there was no grayscale saturation -
- // saturation was the color saturation.
- saturation = configObject["saturation"] || { },
- colorSaturation = "color" in saturation ? saturation["color"] : saturation,
- grayscaleSaturation = saturation["grayscale"],
-
- backColor = configObject["backColor"],
- padding = configObject["padding"];
-
- /**
- * Creates a lightness range.
- */
- function lightness(configName, defaultRange) {
- var range = lightnessConfig[configName];
-
- // Check if the lightness range is an array-like object. This way we ensure the
- // array contain two values at the same time.
- if (!(range && range.length > 1)) {
- range = defaultRange;
- }
-
- /**
- * 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;
- };
- }
-
- /**
- * Gets a hue allowed by the configured hue restriction,
- * provided the originally computed hue.
- */
- function hueFunction(originalHue) {
- var hueConfig = configObject["hues"];
- var hue;
-
- // Check if 'hues' is an array-like object. This way we also ensure that
- // the array is not empty, which would mean no hue restriction.
- if (hueConfig && hueConfig.length > 0) {
- // originalHue is in the range [0, 1]
- // Multiply with 0.999 to change the range to [0, 1) and then truncate the index.
- hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)];
- }
-
- return typeof hue == "number" ?
-
- // A hue was specified. We need to convert the hue from
- // degrees on any turn - e.g. 746° is a perfectly valid hue -
- // to turns in the range [0, 1).
- ((((hue / 360) % 1) + 1) % 1) :
-
- // No hue configured => use original hue
- originalHue;
- }
-
- return {
- X/*hue*/: hueFunction,
- p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5,
- H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0,
- q/*colorLightness*/: lightness("color", [0.4, 0.8]),
- I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]),
- J/*backColor*/: parseColor(backColor),
- Y/*iconPadding*/:
- typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig :
- typeof padding == "number" ? padding :
- defaultPadding
- }
+/**
+ * @typedef {Object} ParsedConfiguration
+ * @property {number} colorSaturation
+ * @property {number} grayscaleSaturation
+ * @property {string} backColor
+ * @property {number} iconPadding
+ * @property {function(number):number} hue
+ * @property {function(number):number} colorLightness
+ * @property {function(number):number} grayscaleLightness
+ */
+
+var CONFIG_PROPERTIES = {
+ G/*GLOBAL*/: "jdenticon_config",
+ n/*MODULE*/: "config",
+};
+
+var rootConfigurationHolder = {};
+
+/**
+ * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console
+ * when it is being used.
+ * @param {!Object} rootObject
+ */
+function defineConfigProperty(rootObject) {
+ rootConfigurationHolder = rootObject;
}
-var ICON_TYPE_SVG = 1;
-
-var ICON_TYPE_CANVAS = 2;
-
-var ATTRIBUTES = {
- t/*HASH*/: "data-jdenticon-hash",
- o/*VALUE*/: "data-jdenticon-value"
-};
-
-var IS_RENDERED_PROPERTY = "jdenticonRendered";
-
-var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]";
-
-var documentQuerySelectorAll = /** @type {!Function} */ (
- typeof document !== "undefined" && document.querySelectorAll.bind(document));
-
-function getIdenticonType(el) {
- if (el) {
- var tagName = el["tagName"];
-
- if (/^svg$/i.test(tagName)) {
- return ICON_TYPE_SVG;
- }
-
- if (/^canvas$/i.test(tagName) && "getContext" in el) {
- return ICON_TYPE_CANVAS;
- }
- }
-}
-
-function whenDocumentIsReady(/** @type {Function} */ callback) {
- function loadedHandler() {
- document.removeEventListener("DOMContentLoaded", loadedHandler);
- window.removeEventListener("load", loadedHandler);
- setTimeout(callback, 0); // Give scripts a chance to run
- }
-
- if (typeof document !== "undefined" &&
- typeof window !== "undefined" &&
- typeof setTimeout !== "undefined"
- ) {
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", loadedHandler);
- window.addEventListener("load", loadedHandler);
- } else {
- // Document already loaded. The load events above likely won't be raised
- setTimeout(callback, 0);
- }
- }
+/**
+ * Sets a new icon style configuration. The new configuration is not merged with the previous one. *
+ * @param {Object} newConfiguration - New configuration object.
+ */
+function configure(newConfiguration) {
+ if (arguments.length) {
+ rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration;
+ }
+ return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/];
}
-function observer(updateCallback) {
- if (typeof MutationObserver != "undefined") {
- var mutationObserver = new MutationObserver(function onmutation(mutations) {
- for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) {
- var mutation = mutations[mutationIndex];
- var addedNodes = mutation.addedNodes;
-
- for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) {
- var addedNode = addedNodes[addedNodeIndex];
-
- // Skip other types of nodes than element nodes, since they might not support
- // the querySelectorAll method => runtime error.
- if (addedNode.nodeType == 1) {
- if (getIdenticonType(addedNode)) {
- updateCallback(addedNode);
- }
- else {
- var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR);
- for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) {
- updateCallback(icons[iconIndex]);
- }
- }
- }
- }
-
- if (mutation.type == "attributes" && getIdenticonType(mutation.target)) {
- updateCallback(mutation.target);
- }
- }
- });
-
- mutationObserver.observe(document.body, {
- "childList": true,
- "attributes": true,
- "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"],
- "subtree": true,
- });
- }
+/**
+ * Gets the normalized current Jdenticon color configuration. Missing fields have default values.
+ * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A
+ * local configuration overrides the global configuration in it entirety. This parameter can for backward
+ * compatibility also contain a padding value. A padding value only overrides the global padding, not the
+ * entire global configuration.
+ * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor
+ * explicitly to the API method.
+ * @returns {ParsedConfiguration}
+ */
+function getConfiguration(paddingOrLocalConfig, defaultPadding) {
+ var configObject =
+ typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig ||
+ rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] ||
+ GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
+ { },
+
+ lightnessConfig = configObject["lightness"] || { },
+
+ // In versions < 2.1.0 there was no grayscale saturation -
+ // saturation was the color saturation.
+ saturation = configObject["saturation"] || { },
+ colorSaturation = "color" in saturation ? saturation["color"] : saturation,
+ grayscaleSaturation = saturation["grayscale"],
+
+ backColor = configObject["backColor"],
+ padding = configObject["padding"];
+
+ /**
+ * Creates a lightness range.
+ */
+ function lightness(configName, defaultRange) {
+ var range = lightnessConfig[configName];
+
+ // Check if the lightness range is an array-like object. This way we ensure the
+ // array contain two values at the same time.
+ if (!(range && range.length > 1)) {
+ range = defaultRange;
+ }
+
+ /**
+ * 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;
+ };
+ }
+
+ /**
+ * Gets a hue allowed by the configured hue restriction,
+ * provided the originally computed hue.
+ */
+ function hueFunction(originalHue) {
+ var hueConfig = configObject["hues"];
+ var hue;
+
+ // Check if 'hues' is an array-like object. This way we also ensure that
+ // the array is not empty, which would mean no hue restriction.
+ if (hueConfig && hueConfig.length > 0) {
+ // originalHue is in the range [0, 1]
+ // Multiply with 0.999 to change the range to [0, 1) and then truncate the index.
+ hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)];
+ }
+
+ return typeof hue == "number" ?
+
+ // A hue was specified. We need to convert the hue from
+ // degrees on any turn - e.g. 746° is a perfectly valid hue -
+ // to turns in the range [0, 1).
+ ((((hue / 360) % 1) + 1) % 1) :
+
+ // No hue configured => use original hue
+ originalHue;
+ }
+
+ return {
+ X/*hue*/: hueFunction,
+ p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5,
+ H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0,
+ q/*colorLightness*/: lightness("color", [0.4, 0.8]),
+ I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]),
+ J/*backColor*/: parseColor(backColor),
+ Y/*iconPadding*/:
+ typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig :
+ typeof padding == "number" ? padding :
+ defaultPadding
+ }
}
-/**
- * Represents a point.
- */
-function Point(x, y) {
- this.x = x;
- this.y = y;
+var ICON_TYPE_SVG = 1;
+
+var ICON_TYPE_CANVAS = 2;
+
+var ATTRIBUTES = {
+ t/*HASH*/: "data-jdenticon-hash",
+ o/*VALUE*/: "data-jdenticon-value"
+};
+
+var IS_RENDERED_PROPERTY = "jdenticonRendered";
+
+var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]";
+
+var documentQuerySelectorAll = /** @type {!Function} */ (
+ typeof document !== "undefined" && document.querySelectorAll.bind(document));
+
+function getIdenticonType(el) {
+ if (el) {
+ var tagName = el["tagName"];
+
+ if (/^svg$/i.test(tagName)) {
+ return ICON_TYPE_SVG;
+ }
+
+ if (/^canvas$/i.test(tagName) && "getContext" in el) {
+ return ICON_TYPE_CANVAS;
+ }
+ }
}
-/**
- * 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.
- */
-function Transform(x, y, size, rotation) {
- this.u/*_x*/ = x;
- this.v/*_y*/ = y;
- this.K/*_size*/ = size;
- this.Z/*_rotation*/ = rotation;
-}
-
-/**
- * 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.
- */
-Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) {
- var right = this.u/*_x*/ + this.K/*_size*/,
- bottom = this.v/*_y*/ + this.K/*_size*/,
- rotation = this.Z/*_rotation*/;
- return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) :
- rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) :
- rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) :
- new Point(this.u/*_x*/ + x, this.v/*_y*/ + y);
-};
-
+function whenDocumentIsReady(/** @type {Function} */ callback) {
+ function loadedHandler() {
+ document.removeEventListener("DOMContentLoaded", loadedHandler);
+ window.removeEventListener("load", loadedHandler);
+ setTimeout(callback, 0); // Give scripts a chance to run
+ }
+
+ if (typeof document !== "undefined" &&
+ typeof window !== "undefined" &&
+ typeof setTimeout !== "undefined"
+ ) {
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", loadedHandler);
+ window.addEventListener("load", loadedHandler);
+ } else {
+ // Document already loaded. The load events above likely won't be raised
+ setTimeout(callback, 0);
+ }
+ }
+}
+
+function observer(updateCallback) {
+ if (typeof MutationObserver != "undefined") {
+ var mutationObserver = new MutationObserver(function onmutation(mutations) {
+ for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) {
+ var mutation = mutations[mutationIndex];
+ var addedNodes = mutation.addedNodes;
+
+ for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) {
+ var addedNode = addedNodes[addedNodeIndex];
+
+ // Skip other types of nodes than element nodes, since they might not support
+ // the querySelectorAll method => runtime error.
+ if (addedNode.nodeType == 1) {
+ if (getIdenticonType(addedNode)) {
+ updateCallback(addedNode);
+ }
+ else {
+ var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR);
+ for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) {
+ updateCallback(icons[iconIndex]);
+ }
+ }
+ }
+ }
+
+ if (mutation.type == "attributes" && getIdenticonType(mutation.target)) {
+ updateCallback(mutation.target);
+ }
+ }
+ });
+
+ mutationObserver.observe(document.body, {
+ "childList": true,
+ "attributes": true,
+ "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"],
+ "subtree": true,
+ });
+ }
+}
+
+/**
+ * Represents a point.
+ */
+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.
+ */
+function Transform(x, y, size, rotation) {
+ this.u/*_x*/ = x;
+ this.v/*_y*/ = y;
+ this.K/*_size*/ = size;
+ this.Z/*_rotation*/ = rotation;
+}
+
+/**
+ * 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.
+ */
+Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) {
+ var right = this.u/*_x*/ + this.K/*_size*/,
+ bottom = this.v/*_y*/ + this.K/*_size*/,
+ rotation = this.Z/*_rotation*/;
+ return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) :
+ rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) :
+ rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) :
+ new Point(this.u/*_x*/ + x, this.v/*_y*/ + y);
+};
+
var NO_TRANSFORM = new Transform(0, 0, 0, 0);
-
-
-/**
- * Provides helper functions for rendering common basic shapes.
- */
-function Graphics(renderer) {
- /**
- * @type {Renderer}
- * @private
- */
- this.M/*_renderer*/ = renderer;
-
- /**
- * @type {Transform}
- */
- this.A/*currentTransform*/ = NO_TRANSFORM;
-}
-var Graphics__prototype = 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.
- */
+
+
+/**
+ * Provides helper functions for rendering common basic shapes.
+ */
+function Graphics(renderer) {
+ /**
+ * @type {Renderer}
+ * @private
+ */
+ this.M/*_renderer*/ = renderer;
+
+ /**
+ * @type {Transform}
+ */
+ this.A/*currentTransform*/ = NO_TRANSFORM;
+}
+var Graphics__prototype = 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.
+ */
Graphics__prototype.g/*addPolygon*/ = function addPolygon (points, invert) {
var this$1 = this;
-
- var di = invert ? -2 : 2,
- transformedPoints = [];
-
- for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) {
- transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1]));
- }
-
- this.M/*_renderer*/.g/*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.
- */
-Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) {
- var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size);
- this.M/*_renderer*/.h/*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.
- */
-Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) {
- this.g/*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.
- */
-Graphics__prototype.j/*addTriangle*/ = function addTriangle (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.g/*addPolygon*/(points, invert);
-};
-
-/**
- * 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.
- */
-Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) {
- this.g/*addPolygon*/([
- x + w / 2, y,
- x + w, y + h / 2,
- x + w / 2, y + h,
- x, y + h / 2
- ], invert);
+
+ var di = invert ? -2 : 2,
+ transformedPoints = [];
+
+ for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) {
+ transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1]));
+ }
+
+ this.M/*_renderer*/.g/*addPolygon*/(transformedPoints);
};
-/**
- * @param {number} index
- * @param {Graphics} g
- * @param {number} cell
- * @param {number} positionIndex
- */
-function centerShape(index, g, cell, positionIndex) {
- index = index % 14;
-
- var k, m, w, h, inner, outer;
-
- !index ? (
- k = cell * 0.42,
- g.g/*addPolygon*/([
- 0, 0,
- cell, 0,
- cell, cell - k * 2,
- cell - k, cell,
- 0, cell
- ])) :
-
- index == 1 ? (
- w = 0 | (cell * 0.5),
- h = 0 | (cell * 0.8),
-
- g.j/*addTriangle*/(cell - w, 0, w, h, 2)) :
-
- index == 2 ? (
- w = 0 | (cell / 3),
- g.i/*addRectangle*/(w, w, cell - w, cell - w)) :
-
- index == 3 ? (
- inner = cell * 0.1,
- // 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)),
-
- inner =
- inner > 1 ? (0 | inner) : // large icon => truncate decimals
- inner > 0.5 ? 1 : // medium size icon => fixed width
- inner, // small icon => anti-aliased border
-
- g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) :
-
- index == 4 ? (
- m = 0 | (cell * 0.15),
- w = 0 | (cell * 0.5),
- g.h/*addCircle*/(cell - w - m, cell - w - m, w)) :
-
- index == 5 ? (
- inner = cell * 0.1,
- outer = inner * 4,
-
- // Align edge to nearest pixel in large icons
- outer > 3 && (outer = 0 | outer),
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.g/*addPolygon*/([
- outer, outer,
- cell - inner, outer,
- outer + (cell - outer - inner) / 2, cell - inner
- ], true)) :
-
- index == 6 ?
- g.g/*addPolygon*/([
- 0, 0,
- cell, 0,
- cell, cell * 0.7,
- cell * 0.4, cell * 0.4,
- cell * 0.7, cell,
- 0, cell
- ]) :
-
- index == 7 ?
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
-
- index == 8 ? (
- g.i/*addRectangle*/(0, 0, cell, cell / 2),
- g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2),
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) :
-
- index == 9 ? (
- inner = cell * 0.14,
- // 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)),
-
- inner =
- cell < 8 ? inner : // small icon => anti-aliased border
- (0 | inner), // large icon => truncate decimals
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) :
-
- index == 10 ? (
- inner = cell * 0.12,
- outer = inner * 3,
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) :
-
- index == 11 ?
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
-
- index == 12 ? (
- m = cell * 0.25,
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) :
-
- // 13
- (
- !positionIndex && (
- m = cell * 0.4, w = cell * 1.2,
- g.h/*addCircle*/(m, m, w)
- )
- );
-}
-
-/**
- * @param {number} index
- * @param {Graphics} g
- * @param {number} cell
- */
-function outerShape(index, g, cell) {
- index = index % 4;
-
- var m;
-
- !index ?
- g.j/*addTriangle*/(0, 0, cell, cell, 0) :
-
- index == 1 ?
- g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) :
-
- index == 2 ?
- g.N/*addRhombus*/(0, 0, cell, cell) :
-
- // 3
- (
- m = cell / 6,
- g.h/*addCircle*/(m, m, cell - 2 * m)
- );
-}
-
-/**
- * Gets a set of identicon color candidates for a specified hue and config.
- * @param {number} hue
- * @param {ParsedConfiguration} config
- */
-function colorTheme(hue, config) {
- hue = config.X/*hue*/(hue);
- return [
- // Dark gray
- correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)),
- // Mid color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)),
- // Light gray
- correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)),
- // Light color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)),
- // Dark color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0))
- ];
-}
-
-/**
- * Draws an identicon to a specified renderer.
- * @param {Renderer} renderer
- * @param {string} hash
- * @param {Object|number=} config
- */
-function iconGenerator(renderer, hash, config) {
- var parsedConfig = getConfiguration(config, 0.08);
-
- // Set background color
- if (parsedConfig.J/*backColor*/) {
- renderer.m/*setBackground*/(parsedConfig.J/*backColor*/);
- }
-
- // Calculate padding and round to nearest integer
- var size = renderer.k/*iconSize*/;
- var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0;
- size -= padding * 2;
-
- 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
- var x = 0 | (padding + size / 2 - cell * 2);
- var y = 0 | (padding + size / 2 - cell * 2);
-
- function renderShape(colorIndex, shapes, index, rotationIndex, positions) {
- var shapeIndex = parseHex(hash, index, 1);
- var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0;
-
- renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]);
-
- for (var i = 0; i < positions.length; i++) {
- graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4);
- shapes(shapeIndex, graphics, cell, i);
- }
-
- renderer.P/*endShape*/();
- }
-
- // AVAILABLE COLORS
- var hue = parseHex(hash, -7) / 0xfffffff,
-
- // Available colors for this icon
- availableColors = colorTheme(hue, parsedConfig),
-
- // The index of the selected colors
- selectedColorIndexes = [];
-
- var 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;
- }
- }
- }
- }
-
- for (var i = 0; i < 3; i++) {
- index = parseHex(hash, 8 + i, 1) % 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, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]);
- // Corners
- renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]);
- // Center
- renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]);
-
- renderer.finish();
-}
-
-/**
- * Computes a SHA1 hash for any value and returns it as a hexadecimal string.
- *
- * This function is optimized for minimal code size and rather short messages.
- *
- * @param {string} message
- */
-function sha1(message) {
- var HASH_SIZE_HALF_BYTES = 40;
- var BLOCK_SIZE_WORDS = 16;
-
- // Variables
- // `var` is used to be able to minimize the number of `var` keywords.
- var i = 0,
- f = 0,
-
- // Use `encodeURI` to UTF8 encode the message without any additional libraries
- // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
- // since `unescape` is deprecated.
- urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
-
- // This can be changed to a preallocated Uint32Array array for greater performance and larger code size
- data = [],
- dataSize,
-
- hashBuffer = [],
-
- a = 0x67452301,
- b = 0xefcdab89,
- c = ~a,
- d = ~b,
- e = 0xc3d2e1f0,
- hash = [a, b, c, d, e],
-
- blockStartIndex = 0,
- hexHash = "";
-
- /**
- * Rotates the value a specified number of bits to the left.
- * @param {number} value Value to rotate
- * @param {number} shift Bit count to shift.
- */
- function rotl(value, shift) {
- return (value << shift) | (value >>> (32 - shift));
- }
-
- // Message data
- for ( ; i < urlEncodedMessage.length; f++) {
- data[f >> 2] = data[f >> 2] |
- (
- (
- urlEncodedMessage[i] == "%"
- // Percent encoded byte
- ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
- // Unencoded byte
- : urlEncodedMessage.charCodeAt(i++)
- )
-
- // Read bytes in reverse order (big endian words)
- << ((3 - (f & 3)) * 8)
- );
- }
-
- // f is now the length of the utf8 encoded message
- // 7 = 8 bytes (64 bit) for message size, -1 to round down
- // >> 6 = integer division with block size
- dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
-
- // Message size in bits.
- // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
- // significant 32 bits are set. -8 is for the '1' bit padding byte.
- data[dataSize - 1] = f * 8 - 8;
-
- // Compute hash
- for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
- for (i = 0; i < 80; i++) {
- f = rotl(a, 5) + e + (
- // Ch
- i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
-
- // Parity
- i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
-
- // Maj
- i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
-
- // Parity
- (b ^ c ^ d) + 0xca62c1d6
- ) + (
- hashBuffer[i] = i < BLOCK_SIZE_WORDS
- // Bitwise OR is used to coerse `undefined` to 0
- ? (data[blockStartIndex + i] | 0)
- : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
- );
-
- e = d;
- d = c;
- c = rotl(b, 30);
- b = a;
- a = f;
- }
-
- hash[0] = a = ((hash[0] + a) | 0);
- hash[1] = b = ((hash[1] + b) | 0);
- hash[2] = c = ((hash[2] + c) | 0);
- hash[3] = d = ((hash[3] + d) | 0);
- hash[4] = e = ((hash[4] + e) | 0);
- }
-
- // Format hex hash
- for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
- hexHash += (
- (
- // Get word (2^3 half-bytes per word)
- hash[i >> 3] >>>
-
- // Append half-bytes in reverse order
- ((7 - (i & 7)) * 4)
- )
- // Clamp to half-byte
- & 0xf
- ).toString(16);
- }
-
- return hexHash;
-}
-
-/**
- * Inputs a value that might be a valid hash string for Jdenticon and returns it
- * if it is determined valid, otherwise a falsy value is returned.
- */
-function isValidHash(hashCandidate) {
- return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate;
-}
-
-/**
- * Computes a hash for the specified value. Currently SHA1 is used. This function
- * always returns a valid hash.
- */
-function computeHash(value) {
- return sha1(value == null ? "" : "" + value);
-}
-
-
-
-/**
- * Renderer redirecting drawing commands to a canvas context.
- * @implements {Renderer}
- */
-function CanvasRenderer(ctx, iconSize) {
- var canvas = ctx.canvas;
- var width = canvas.width;
- var height = canvas.height;
-
- ctx.save();
-
- if (!iconSize) {
- iconSize = Math.min(width, height);
-
- ctx.translate(
- ((width - iconSize) / 2) | 0,
- ((height - iconSize) / 2) | 0);
- }
-
- /**
- * @private
- */
- this.l/*_ctx*/ = ctx;
- this.k/*iconSize*/ = iconSize;
-
- ctx.clearRect(0, 0, iconSize, iconSize);
-}
-var CanvasRenderer__prototype = CanvasRenderer.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb[aa].
- */
-CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
- var ctx = this.l/*_ctx*/;
- var iconSize = this.k/*iconSize*/;
-
- ctx.fillStyle = toCss3Color(fillColor);
- ctx.fillRect(0, 0, iconSize, iconSize);
-};
-
-/**
- * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
- * @param {string} fillColor Fill color on format #rrggbb[aa].
- */
-CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) {
- var ctx = this.l/*_ctx*/;
- ctx.fillStyle = toCss3Color(fillColor);
- ctx.beginPath();
-};
-
-/**
- * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas.
- */
-CanvasRenderer__prototype.P/*endShape*/ = function endShape () {
- this.l/*_ctx*/.fill();
-};
-
-/**
- * Adds a polygon to the rendering queue.
- * @param points An array of Point objects.
- */
-CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
- var ctx = this.l/*_ctx*/;
- ctx.moveTo(points[0].x, points[0].y);
- for (var 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).
- */
-CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- var ctx = this.l/*_ctx*/,
- radius = diameter / 2;
- ctx.moveTo(point.x + radius, point.y + radius);
- ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise);
- ctx.closePath();
-};
-
-/**
- * Called when the icon has been completely drawn.
- */
-CanvasRenderer__prototype.finish = function finish () {
- this.l/*_ctx*/.restore();
+/**
+ * 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.
+ */
+Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) {
+ var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size);
+ this.M/*_renderer*/.h/*addCircle*/(p, size, invert);
};
-/**
- * Draws an identicon to a context.
- * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0).
- * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
- * @param {number} size - Icon size in pixels.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
- * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- */
-function drawIcon(ctx, hashOrValue, size, config) {
- if (!ctx) {
- throw new Error("No canvas specified.");
- }
-
- iconGenerator(new CanvasRenderer(ctx, size),
- isValidHash(hashOrValue) || computeHash(hashOrValue),
- config);
-
- var canvas = ctx.canvas;
- if (canvas) {
- canvas[IS_RENDERED_PROPERTY] = true;
- }
-}
-
-/**
- * Prepares a measure to be used as a measure in an SVG path, by
- * rounding the measure to a single decimal. This reduces the file
- * size of the generated SVG with more than 50% in some cases.
- */
-function svgValue(value) {
- return ((value * 10 + 0.5) | 0) / 10;
-}
-
-/**
- * Represents an SVG path element.
- */
-function SvgPath() {
- /**
- * This property holds the data string (path.d) of the SVG path.
- * @type {string}
- */
- this.B/*dataString*/ = "";
-}
-var SvgPath__prototype = SvgPath.prototype;
-
-/**
- * Adds a polygon with the current fill color to the SVG path.
- * @param points An array of Point objects.
- */
-SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) {
- var dataString = "";
- for (var i = 0; i < points.length; i++) {
- dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y);
- }
- this.B/*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).
- */
-SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- var sweepFlag = counterClockwise ? 0 : 1,
- svgRadius = svgValue(diameter / 2),
- svgDiameter = svgValue(diameter),
- svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " ";
-
- this.B/*dataString*/ +=
- "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) +
- svgArc + svgDiameter + ",0" +
- svgArc + (-svgDiameter) + ",0";
+/**
+ * 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.
+ */
+Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) {
+ this.g/*addPolygon*/([
+ x, y,
+ x + w, y,
+ x + w, y + h,
+ x, y + h
+ ], invert);
};
-
-
-/**
- * Renderer producing SVG output.
- * @implements {Renderer}
- */
-function SvgRenderer(target) {
- /**
- * @type {SvgPath}
- * @private
- */
- this.C/*_path*/;
-
- /**
- * @type {Object.}
- * @private
- */
- this.D/*_pathsByColor*/ = { };
-
- /**
- * @type {SvgElement|SvgWriter}
- * @private
- */
- this.R/*_target*/ = target;
-
- /**
- * @type {number}
- */
- this.k/*iconSize*/ = target.k/*iconSize*/;
-}
-var SvgRenderer__prototype = SvgRenderer.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb[aa].
- */
-SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
- var match = /^(#......)(..)?/.exec(fillColor),
- opacity = match[2] ? parseHex(match[2], 0) / 255 : 1;
- this.R/*_target*/.m/*setBackground*/(match[1], opacity);
-};
-
-/**
- * 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.
- */
-SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) {
- this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath());
-};
-
-/**
- * Marks the end of the currently drawn shape.
- */
-SvgRenderer__prototype.P/*endShape*/ = function endShape () { };
-
-/**
- * Adds a polygon with the current fill color to the SVG.
- * @param points An array of Point objects.
- */
-SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
- this.C/*_path*/.g/*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).
- */
-SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise);
-};
-
-/**
- * Called when the icon has been completely drawn.
- */
+/**
+ * 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.
+ */
+Graphics__prototype.j/*addTriangle*/ = function addTriangle (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.g/*addPolygon*/(points, invert);
+};
+
+/**
+ * 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.
+ */
+Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) {
+ this.g/*addPolygon*/([
+ x + w / 2, y,
+ x + w, y + h / 2,
+ x + w / 2, y + h,
+ x, y + h / 2
+ ], invert);
+};
+
+/**
+ * @param {number} index
+ * @param {Graphics} g
+ * @param {number} cell
+ * @param {number} positionIndex
+ */
+function centerShape(index, g, cell, positionIndex) {
+ index = index % 14;
+
+ var k, m, w, h, inner, outer;
+
+ !index ? (
+ k = cell * 0.42,
+ g.g/*addPolygon*/([
+ 0, 0,
+ cell, 0,
+ cell, cell - k * 2,
+ cell - k, cell,
+ 0, cell
+ ])) :
+
+ index == 1 ? (
+ w = 0 | (cell * 0.5),
+ h = 0 | (cell * 0.8),
+
+ g.j/*addTriangle*/(cell - w, 0, w, h, 2)) :
+
+ index == 2 ? (
+ w = 0 | (cell / 3),
+ g.i/*addRectangle*/(w, w, cell - w, cell - w)) :
+
+ index == 3 ? (
+ inner = cell * 0.1,
+ // 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)),
+
+ inner =
+ inner > 1 ? (0 | inner) : // large icon => truncate decimals
+ inner > 0.5 ? 1 : // medium size icon => fixed width
+ inner, // small icon => anti-aliased border
+
+ g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) :
+
+ index == 4 ? (
+ m = 0 | (cell * 0.15),
+ w = 0 | (cell * 0.5),
+ g.h/*addCircle*/(cell - w - m, cell - w - m, w)) :
+
+ index == 5 ? (
+ inner = cell * 0.1,
+ outer = inner * 4,
+
+ // Align edge to nearest pixel in large icons
+ outer > 3 && (outer = 0 | outer),
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.g/*addPolygon*/([
+ outer, outer,
+ cell - inner, outer,
+ outer + (cell - outer - inner) / 2, cell - inner
+ ], true)) :
+
+ index == 6 ?
+ g.g/*addPolygon*/([
+ 0, 0,
+ cell, 0,
+ cell, cell * 0.7,
+ cell * 0.4, cell * 0.4,
+ cell * 0.7, cell,
+ 0, cell
+ ]) :
+
+ index == 7 ?
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
+
+ index == 8 ? (
+ g.i/*addRectangle*/(0, 0, cell, cell / 2),
+ g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2),
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) :
+
+ index == 9 ? (
+ inner = cell * 0.14,
+ // 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)),
+
+ inner =
+ cell < 8 ? inner : // small icon => anti-aliased border
+ (0 | inner), // large icon => truncate decimals
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) :
+
+ index == 10 ? (
+ inner = cell * 0.12,
+ outer = inner * 3,
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) :
+
+ index == 11 ?
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
+
+ index == 12 ? (
+ m = cell * 0.25,
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) :
+
+ // 13
+ (
+ !positionIndex && (
+ m = cell * 0.4, w = cell * 1.2,
+ g.h/*addCircle*/(m, m, w)
+ )
+ );
+}
+
+/**
+ * @param {number} index
+ * @param {Graphics} g
+ * @param {number} cell
+ */
+function outerShape(index, g, cell) {
+ index = index % 4;
+
+ var m;
+
+ !index ?
+ g.j/*addTriangle*/(0, 0, cell, cell, 0) :
+
+ index == 1 ?
+ g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) :
+
+ index == 2 ?
+ g.N/*addRhombus*/(0, 0, cell, cell) :
+
+ // 3
+ (
+ m = cell / 6,
+ g.h/*addCircle*/(m, m, cell - 2 * m)
+ );
+}
+
+/**
+ * Gets a set of identicon color candidates for a specified hue and config.
+ * @param {number} hue
+ * @param {ParsedConfiguration} config
+ */
+function colorTheme(hue, config) {
+ hue = config.X/*hue*/(hue);
+ return [
+ // Dark gray
+ correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)),
+ // Mid color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)),
+ // Light gray
+ correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)),
+ // Light color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)),
+ // Dark color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0))
+ ];
+}
+
+/**
+ * Draws an identicon to a specified renderer.
+ * @param {Renderer} renderer
+ * @param {string} hash
+ * @param {Object|number=} config
+ */
+function iconGenerator(renderer, hash, config) {
+ var parsedConfig = getConfiguration(config, 0.08);
+
+ // Set background color
+ if (parsedConfig.J/*backColor*/) {
+ renderer.m/*setBackground*/(parsedConfig.J/*backColor*/);
+ }
+
+ // Calculate padding and round to nearest integer
+ var size = renderer.k/*iconSize*/;
+ var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0;
+ size -= padding * 2;
+
+ 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
+ var x = 0 | (padding + size / 2 - cell * 2);
+ var y = 0 | (padding + size / 2 - cell * 2);
+
+ function renderShape(colorIndex, shapes, index, rotationIndex, positions) {
+ var shapeIndex = parseHex(hash, index, 1);
+ var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0;
+
+ renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]);
+
+ for (var i = 0; i < positions.length; i++) {
+ graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4);
+ shapes(shapeIndex, graphics, cell, i);
+ }
+
+ renderer.P/*endShape*/();
+ }
+
+ // AVAILABLE COLORS
+ var hue = parseHex(hash, -7) / 0xfffffff,
+
+ // Available colors for this icon
+ availableColors = colorTheme(hue, parsedConfig),
+
+ // The index of the selected colors
+ selectedColorIndexes = [];
+
+ var 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;
+ }
+ }
+ }
+ }
+
+ for (var i = 0; i < 3; i++) {
+ index = parseHex(hash, 8 + i, 1) % 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, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]);
+ // Corners
+ renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]);
+ // Center
+ renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]);
+
+ renderer.finish();
+}
+
+/**
+ * Computes a SHA1 hash for any value and returns it as a hexadecimal string.
+ *
+ * This function is optimized for minimal code size and rather short messages.
+ *
+ * @param {string} message
+ */
+function sha1(message) {
+ var HASH_SIZE_HALF_BYTES = 40;
+ var BLOCK_SIZE_WORDS = 16;
+
+ // Variables
+ // `var` is used to be able to minimize the number of `var` keywords.
+ var i = 0,
+ f = 0,
+
+ // Use `encodeURI` to UTF8 encode the message without any additional libraries
+ // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
+ // since `unescape` is deprecated.
+ urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
+
+ // This can be changed to a preallocated Uint32Array array for greater performance and larger code size
+ data = [],
+ dataSize,
+
+ hashBuffer = [],
+
+ a = 0x67452301,
+ b = 0xefcdab89,
+ c = ~a,
+ d = ~b,
+ e = 0xc3d2e1f0,
+ hash = [a, b, c, d, e],
+
+ blockStartIndex = 0,
+ hexHash = "";
+
+ /**
+ * Rotates the value a specified number of bits to the left.
+ * @param {number} value Value to rotate
+ * @param {number} shift Bit count to shift.
+ */
+ function rotl(value, shift) {
+ return (value << shift) | (value >>> (32 - shift));
+ }
+
+ // Message data
+ for ( ; i < urlEncodedMessage.length; f++) {
+ data[f >> 2] = data[f >> 2] |
+ (
+ (
+ urlEncodedMessage[i] == "%"
+ // Percent encoded byte
+ ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
+ // Unencoded byte
+ : urlEncodedMessage.charCodeAt(i++)
+ )
+
+ // Read bytes in reverse order (big endian words)
+ << ((3 - (f & 3)) * 8)
+ );
+ }
+
+ // f is now the length of the utf8 encoded message
+ // 7 = 8 bytes (64 bit) for message size, -1 to round down
+ // >> 6 = integer division with block size
+ dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
+
+ // Message size in bits.
+ // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
+ // significant 32 bits are set. -8 is for the '1' bit padding byte.
+ data[dataSize - 1] = f * 8 - 8;
+
+ // Compute hash
+ for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
+ for (i = 0; i < 80; i++) {
+ f = rotl(a, 5) + e + (
+ // Ch
+ i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
+
+ // Parity
+ i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
+
+ // Maj
+ i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
+
+ // Parity
+ (b ^ c ^ d) + 0xca62c1d6
+ ) + (
+ hashBuffer[i] = i < BLOCK_SIZE_WORDS
+ // Bitwise OR is used to coerse `undefined` to 0
+ ? (data[blockStartIndex + i] | 0)
+ : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
+ );
+
+ e = d;
+ d = c;
+ c = rotl(b, 30);
+ b = a;
+ a = f;
+ }
+
+ hash[0] = a = ((hash[0] + a) | 0);
+ hash[1] = b = ((hash[1] + b) | 0);
+ hash[2] = c = ((hash[2] + c) | 0);
+ hash[3] = d = ((hash[3] + d) | 0);
+ hash[4] = e = ((hash[4] + e) | 0);
+ }
+
+ // Format hex hash
+ for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
+ hexHash += (
+ (
+ // Get word (2^3 half-bytes per word)
+ hash[i >> 3] >>>
+
+ // Append half-bytes in reverse order
+ ((7 - (i & 7)) * 4)
+ )
+ // Clamp to half-byte
+ & 0xf
+ ).toString(16);
+ }
+
+ return hexHash;
+}
+
+/**
+ * Inputs a value that might be a valid hash string for Jdenticon and returns it
+ * if it is determined valid, otherwise a falsy value is returned.
+ */
+function isValidHash(hashCandidate) {
+ return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate;
+}
+
+/**
+ * Computes a hash for the specified value. Currently SHA1 is used. This function
+ * always returns a valid hash.
+ */
+function computeHash(value) {
+ return sha1(value == null ? "" : "" + value);
+}
+
+
+
+/**
+ * Renderer redirecting drawing commands to a canvas context.
+ * @implements {Renderer}
+ */
+function CanvasRenderer(ctx, iconSize) {
+ var canvas = ctx.canvas;
+ var width = canvas.width;
+ var height = canvas.height;
+
+ ctx.save();
+
+ if (!iconSize) {
+ iconSize = Math.min(width, height);
+
+ ctx.translate(
+ ((width - iconSize) / 2) | 0,
+ ((height - iconSize) / 2) | 0);
+ }
+
+ /**
+ * @private
+ */
+ this.l/*_ctx*/ = ctx;
+ this.k/*iconSize*/ = iconSize;
+
+ ctx.clearRect(0, 0, iconSize, iconSize);
+}
+var CanvasRenderer__prototype = CanvasRenderer.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb[aa].
+ */
+CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
+ var ctx = this.l/*_ctx*/;
+ var iconSize = this.k/*iconSize*/;
+
+ ctx.fillStyle = toCss3Color(fillColor);
+ ctx.fillRect(0, 0, iconSize, iconSize);
+};
+
+/**
+ * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
+ * @param {string} fillColor Fill color on format #rrggbb[aa].
+ */
+CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) {
+ var ctx = this.l/*_ctx*/;
+ ctx.fillStyle = toCss3Color(fillColor);
+ ctx.beginPath();
+};
+
+/**
+ * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas.
+ */
+CanvasRenderer__prototype.P/*endShape*/ = function endShape () {
+ this.l/*_ctx*/.fill();
+};
+
+/**
+ * Adds a polygon to the rendering queue.
+ * @param points An array of Point objects.
+ */
+CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ var ctx = this.l/*_ctx*/;
+ ctx.moveTo(points[0].x, points[0].y);
+ for (var 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).
+ */
+CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ var ctx = this.l/*_ctx*/,
+ radius = diameter / 2;
+ ctx.moveTo(point.x + radius, point.y + radius);
+ ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise);
+ ctx.closePath();
+};
+
+/**
+ * Called when the icon has been completely drawn.
+ */
+CanvasRenderer__prototype.finish = function finish () {
+ this.l/*_ctx*/.restore();
+};
+
+/**
+ * Draws an identicon to a context.
+ * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0).
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
+ * @param {number} size - Icon size in pixels.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ */
+function drawIcon(ctx, hashOrValue, size, config) {
+ if (!ctx) {
+ throw new Error("No canvas specified.");
+ }
+
+ iconGenerator(new CanvasRenderer(ctx, size),
+ isValidHash(hashOrValue) || computeHash(hashOrValue),
+ config);
+
+ var canvas = ctx.canvas;
+ if (canvas) {
+ canvas[IS_RENDERED_PROPERTY] = true;
+ }
+}
+
+/**
+ * Prepares a measure to be used as a measure in an SVG path, by
+ * rounding the measure to a single decimal. This reduces the file
+ * size of the generated SVG with more than 50% in some cases.
+ */
+function svgValue(value) {
+ return ((value * 10 + 0.5) | 0) / 10;
+}
+
+/**
+ * Represents an SVG path element.
+ */
+function SvgPath() {
+ /**
+ * This property holds the data string (path.d) of the SVG path.
+ * @type {string}
+ */
+ this.B/*dataString*/ = "";
+}
+var SvgPath__prototype = SvgPath.prototype;
+
+/**
+ * Adds a polygon with the current fill color to the SVG path.
+ * @param points An array of Point objects.
+ */
+SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ var dataString = "";
+ for (var i = 0; i < points.length; i++) {
+ dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y);
+ }
+ this.B/*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).
+ */
+SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ var sweepFlag = counterClockwise ? 0 : 1,
+ svgRadius = svgValue(diameter / 2),
+ svgDiameter = svgValue(diameter),
+ svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " ";
+
+ this.B/*dataString*/ +=
+ "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) +
+ svgArc + svgDiameter + ",0" +
+ svgArc + (-svgDiameter) + ",0";
+};
+
+
+
+/**
+ * Renderer producing SVG output.
+ * @implements {Renderer}
+ */
+function SvgRenderer(target) {
+ /**
+ * @type {SvgPath}
+ * @private
+ */
+ this.C/*_path*/;
+
+ /**
+ * @type {Object.}
+ * @private
+ */
+ this.D/*_pathsByColor*/ = { };
+
+ /**
+ * @type {SvgElement|SvgWriter}
+ * @private
+ */
+ this.R/*_target*/ = target;
+
+ /**
+ * @type {number}
+ */
+ this.k/*iconSize*/ = target.k/*iconSize*/;
+}
+var SvgRenderer__prototype = SvgRenderer.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb[aa].
+ */
+SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
+ var match = /^(#......)(..)?/.exec(fillColor),
+ opacity = match[2] ? parseHex(match[2], 0) / 255 : 1;
+ this.R/*_target*/.m/*setBackground*/(match[1], opacity);
+};
+
+/**
+ * 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.
+ */
+SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) {
+ this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath());
+};
+
+/**
+ * Marks the end of the currently drawn shape.
+ */
+SvgRenderer__prototype.P/*endShape*/ = function endShape () { };
+
+/**
+ * Adds a polygon with the current fill color to the SVG.
+ * @param points An array of Point objects.
+ */
+SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ this.C/*_path*/.g/*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).
+ */
+SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise);
+};
+
+/**
+ * Called when the icon has been completely drawn.
+ */
SvgRenderer__prototype.finish = function finish () {
var this$1 = this;
-
- var pathsByColor = this.D/*_pathsByColor*/;
- for (var color in pathsByColor) {
- // hasOwnProperty cannot be shadowed in pathsByColor
- // eslint-disable-next-line no-prototype-builtins
- if (pathsByColor.hasOwnProperty(color)) {
- this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/);
- }
- }
+
+ var pathsByColor = this.D/*_pathsByColor*/;
+ for (var color in pathsByColor) {
+ // hasOwnProperty cannot be shadowed in pathsByColor
+ // eslint-disable-next-line no-prototype-builtins
+ if (pathsByColor.hasOwnProperty(color)) {
+ this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/);
+ }
+ }
};
-var SVG_CONSTANTS = {
- T/*XMLNS*/: "http://www.w3.org/2000/svg",
- U/*WIDTH*/: "width",
- V/*HEIGHT*/: "height",
+var SVG_CONSTANTS = {
+ T/*XMLNS*/: "http://www.w3.org/2000/svg",
+ U/*WIDTH*/: "width",
+ V/*HEIGHT*/: "height",
};
-/**
- * Renderer producing SVG output.
- */
-function SvgWriter(iconSize) {
- /**
- * @type {number}
- */
- this.k/*iconSize*/ = iconSize;
-
- /**
- * @type {string}
- * @private
- */
- this.F/*_s*/ =
- '";
+/**
+ * Renderer producing SVG output.
+ */
+function SvgWriter(iconSize) {
+ /**
+ * @type {number}
+ */
+ this.k/*iconSize*/ = iconSize;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.F/*_s*/ =
+ '";
+};
+
+/**
+ * Draws an identicon as an SVG string.
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
+ * @param {number} size - Icon size in pixels.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ * @returns {string} SVG string
+ */
+function toSvg(hashOrValue, size, config) {
+ var writer = new SvgWriter(size);
+ iconGenerator(new SvgRenderer(writer),
+ isValidHash(hashOrValue) || computeHash(hashOrValue),
+ config);
+ return writer.toString();
}
-/**
- * Creates a new element and adds it to the specified parent.
- * @param {Element} parentNode
- * @param {string} name
- * @param {...(string|number)} keyValuePairs
- */
+/**
+ * Creates a new element and adds it to the specified parent.
+ * @param {Element} parentNode
+ * @param {string} name
+ * @param {...(string|number)} keyValuePairs
+ */
function SvgElement_append(parentNode, name) {
var keyValuePairs = [], len = arguments.length - 2;
while ( len-- > 0 ) keyValuePairs[ len ] = arguments[ len + 2 ];
-
- var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name);
-
- for (var i = 0; i + 1 < keyValuePairs.length; i += 2) {
- el.setAttribute(
- /** @type {string} */(keyValuePairs[i]),
- /** @type {string} */(keyValuePairs[i + 1])
- );
- }
-
- parentNode.appendChild(el);
-}
-
-
-/**
- * Renderer producing SVG output.
- */
-function SvgElement(element) {
- // Don't use the clientWidth and clientHeight properties on SVG elements
- // since Firefox won't serve a proper value of these properties on SVG
- // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811)
- // Instead use 100px as a hardcoded size (the svg viewBox will rescale
- // the icon to the correct dimensions)
- var iconSize = this.k/*iconSize*/ = Math.min(
- (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100),
- (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100)
- );
-
- /**
- * @type {Element}
- * @private
- */
- this.W/*_el*/ = element;
-
- // Clear current SVG child elements
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
-
- // Set viewBox attribute to ensure the svg scales nicely.
- element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize);
- element.setAttribute("preserveAspectRatio", "xMidYMid meet");
-}
-var SvgElement__prototype = SvgElement.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb.
- * @param {number} opacity Opacity in the range [0.0, 1.0].
- */
-SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
- if (opacity) {
- SvgElement_append(this.W/*_el*/, "rect",
- SVG_CONSTANTS.U/*WIDTH*/, "100%",
- SVG_CONSTANTS.V/*HEIGHT*/, "100%",
- "fill", fillColor,
- "opacity", opacity);
- }
-};
-
-/**
- * Appends a path to the SVG element.
- * @param {string} color Fill color on format #xxxxxx.
- * @param {string} dataString The SVG path data string.
- */
-SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
- SvgElement_append(this.W/*_el*/, "path",
- "fill", color,
- "d", dataString);
+
+ var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name);
+
+ for (var i = 0; i + 1 < keyValuePairs.length; i += 2) {
+ el.setAttribute(
+ /** @type {string} */(keyValuePairs[i]),
+ /** @type {string} */(keyValuePairs[i + 1])
+ );
+ }
+
+ parentNode.appendChild(el);
+}
+
+
+/**
+ * Renderer producing SVG output.
+ */
+function SvgElement(element) {
+ // Don't use the clientWidth and clientHeight properties on SVG elements
+ // since Firefox won't serve a proper value of these properties on SVG
+ // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811)
+ // Instead use 100px as a hardcoded size (the svg viewBox will rescale
+ // the icon to the correct dimensions)
+ var iconSize = this.k/*iconSize*/ = Math.min(
+ (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100),
+ (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100)
+ );
+
+ /**
+ * @type {Element}
+ * @private
+ */
+ this.W/*_el*/ = element;
+
+ // Clear current SVG child elements
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+
+ // Set viewBox attribute to ensure the svg scales nicely.
+ element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize);
+ element.setAttribute("preserveAspectRatio", "xMidYMid meet");
+}
+var SvgElement__prototype = SvgElement.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb.
+ * @param {number} opacity Opacity in the range [0.0, 1.0].
+ */
+SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
+ if (opacity) {
+ SvgElement_append(this.W/*_el*/, "rect",
+ SVG_CONSTANTS.U/*WIDTH*/, "100%",
+ SVG_CONSTANTS.V/*HEIGHT*/, "100%",
+ "fill", fillColor,
+ "opacity", opacity);
+ }
};
-/**
- * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute.
- */
-function updateAll() {
- if (documentQuerySelectorAll) {
- update(ICON_SELECTOR);
- }
-}
-
-/**
- * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute that have not already
- * been rendered.
- */
-function updateAllConditional() {
- if (documentQuerySelectorAll) {
- /** @type {NodeListOf} */
- var elements = documentQuerySelectorAll(ICON_SELECTOR);
-
- for (var i = 0; i < elements.length; i++) {
- var el = elements[i];
- if (!el[IS_RENDERED_PROPERTY]) {
- update(el);
- }
- }
- }
-}
-
-/**
- * Updates the identicon in the specified `