diff options
Diffstat (limited to 'etc/js/holder.js')
| -rw-r--r-- | etc/js/holder.js | 3070 |
1 files changed, 3070 insertions, 0 deletions
diff --git a/etc/js/holder.js b/etc/js/holder.js new file mode 100644 index 00000000..c116cdb4 --- /dev/null +++ b/etc/js/holder.js @@ -0,0 +1,3070 @@ +/*! + +Holder - client side image placeholders +Version 2.9.6+fblyy +© 2018 Ivan Malopinsky - http://imsky.co + +Site: http://holderjs.com +Issues: https://github.com/imsky/holder/issues +License: MIT + +*/ +(function (window) { + if (!window.document) return; + var document = window.document; + + //https://github.com/inexorabletash/polyfill/blob/master/web.js + if (!document.querySelectorAll) { + document.querySelectorAll = function (selectors) { + var style = document.createElement('style'), elements = [], element; + document.documentElement.firstChild.appendChild(style); + document._qsa = []; + + style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}'; + window.scrollBy(0, 0); + style.parentNode.removeChild(style); + + while (document._qsa.length) { + element = document._qsa.shift(); + element.style.removeAttribute('x-qsa'); + elements.push(element); + } + document._qsa = null; + return elements; + }; + } + + if (!document.querySelector) { + document.querySelector = function (selectors) { + var elements = document.querySelectorAll(selectors); + return (elements.length) ? elements[0] : null; + }; + } + + if (!document.getElementsByClassName) { + document.getElementsByClassName = function (classNames) { + classNames = String(classNames).replace(/^|\s+/g, '.'); + return document.querySelectorAll(classNames); + }; + } + + //https://github.com/inexorabletash/polyfill + // ES5 15.2.3.14 Object.keys ( O ) + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys + if (!Object.keys) { + Object.keys = function (o) { + if (o !== Object(o)) { throw TypeError('Object.keys called on non-object'); } + var ret = [], p; + for (p in o) { + if (Object.prototype.hasOwnProperty.call(o, p)) { + ret.push(p); + } + } + return ret; + }; + } + + // ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] ) + // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach + if (!Array.prototype.forEach) { + Array.prototype.forEach = function (fun /*, thisp */) { + if (this === void 0 || this === null) { throw TypeError(); } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { throw TypeError(); } + + var thisp = arguments[1], i; + for (i = 0; i < len; i++) { + if (i in t) { + fun.call(thisp, t[i], i, t); + } + } + }; + } + + //https://github.com/inexorabletash/polyfill/blob/master/web.js + (function (global) { + var B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + global.atob = global.atob || function (input) { + input = String(input); + var position = 0, + output = [], + buffer = 0, bits = 0, n; + + input = input.replace(/\s/g, ''); + if ((input.length % 4) === 0) { input = input.replace(/=+$/, ''); } + if ((input.length % 4) === 1) { throw Error('InvalidCharacterError'); } + if (/[^+/0-9A-Za-z]/.test(input)) { throw Error('InvalidCharacterError'); } + + while (position < input.length) { + n = B64_ALPHABET.indexOf(input.charAt(position)); + buffer = (buffer << 6) | n; + bits += 6; + + if (bits === 24) { + output.push(String.fromCharCode((buffer >> 16) & 0xFF)); + output.push(String.fromCharCode((buffer >> 8) & 0xFF)); + output.push(String.fromCharCode(buffer & 0xFF)); + bits = 0; + buffer = 0; + } + position += 1; + } + + if (bits === 12) { + buffer = buffer >> 4; + output.push(String.fromCharCode(buffer & 0xFF)); + } else if (bits === 18) { + buffer = buffer >> 2; + output.push(String.fromCharCode((buffer >> 8) & 0xFF)); + output.push(String.fromCharCode(buffer & 0xFF)); + } + + return output.join(''); + }; + + global.btoa = global.btoa || function (input) { + input = String(input); + var position = 0, + out = [], + o1, o2, o3, + e1, e2, e3, e4; + + if (/[^\x00-\xFF]/.test(input)) { throw Error('InvalidCharacterError'); } + + while (position < input.length) { + o1 = input.charCodeAt(position++); + o2 = input.charCodeAt(position++); + o3 = input.charCodeAt(position++); + + // 111111 112222 222233 333333 + e1 = o1 >> 2; + e2 = ((o1 & 0x3) << 4) | (o2 >> 4); + e3 = ((o2 & 0xf) << 2) | (o3 >> 6); + e4 = o3 & 0x3f; + + if (position === input.length + 2) { + e3 = 64; e4 = 64; + } + else if (position === input.length + 1) { + e4 = 64; + } + + out.push(B64_ALPHABET.charAt(e1), + B64_ALPHABET.charAt(e2), + B64_ALPHABET.charAt(e3), + B64_ALPHABET.charAt(e4)); + } + + return out.join(''); + }; + }(window)); + + //https://gist.github.com/jimeh/332357 + if (!Object.prototype.hasOwnProperty){ + /*jshint -W001, -W103 */ + Object.prototype.hasOwnProperty = function(prop) { + var proto = this.__proto__ || this.constructor.prototype; + return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]); + }; + /*jshint +W001, +W103 */ + } + + // @license http://opensource.org/licenses/MIT + // copyright Paul Irish 2015 + + + // Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill + // github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js + // as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values + + // if you want values similar to what you'd get with real perf.now, place this towards the head of the page + // but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed + + + (function(){ + + if ('performance' in window === false) { + window.performance = {}; + } + + Date.now = (Date.now || function () { // thanks IE8 + return new Date().getTime(); + }); + + if ('now' in window.performance === false){ + + var nowOffset = Date.now(); + + if (performance.timing && performance.timing.navigationStart){ + nowOffset = performance.timing.navigationStart; + } + + window.performance.now = function now(){ + return Date.now() - nowOffset; + }; + } + + })(); + + //requestAnimationFrame polyfill for older Firefox/Chrome versions + if (!window.requestAnimationFrame) { + if (window.webkitRequestAnimationFrame && window.webkitCancelAnimationFrame) { + //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-webkit.js + (function (global) { + global.requestAnimationFrame = function (callback) { + return webkitRequestAnimationFrame(function () { + callback(global.performance.now()); + }); + }; + + global.cancelAnimationFrame = global.webkitCancelAnimationFrame; + }(window)); + } else if (window.mozRequestAnimationFrame && window.mozCancelAnimationFrame) { + //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-moz.js + (function (global) { + global.requestAnimationFrame = function (callback) { + return mozRequestAnimationFrame(function () { + callback(global.performance.now()); + }); + }; + + global.cancelAnimationFrame = global.mozCancelAnimationFrame; + }(window)); + } else { + (function (global) { + global.requestAnimationFrame = function (callback) { + return global.setTimeout(callback, 1000 / 60); + }; + + global.cancelAnimationFrame = global.clearTimeout; + })(window); + } + } +})(this); + +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["Holder"] = factory(); + else + root["Holder"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + /* + Holder.js - client side image placeholders + (c) 2012-2015 Ivan Malopinsky - http://imsky.co + */ + + module.exports = __webpack_require__(1); + + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/* + Holder.js - client side image placeholders + (c) 2012-2016 Ivan Malopinsky - http://imsky.co + */ + + //Libraries and functions + var onDomReady = __webpack_require__(2); + var querystring = __webpack_require__(3); + + var SceneGraph = __webpack_require__(6); + var utils = __webpack_require__(7); + var SVG = __webpack_require__(8); + var DOM = __webpack_require__(9); + var Color = __webpack_require__(10); + var constants = __webpack_require__(11); + + var svgRenderer = __webpack_require__(12); + var sgCanvasRenderer = __webpack_require__(15); + + var extend = utils.extend; + var dimensionCheck = utils.dimensionCheck; + + //Constants and definitions + var SVG_NS = constants.svg_ns; + + var Holder = { + version: constants.version, + + /** + * Adds a theme to default settings + * + * @param {string} name Theme name + * @param {Object} theme Theme object, with foreground, background, size, font, and fontweight properties. + */ + addTheme: function(name, theme) { + name != null && theme != null && (App.settings.themes[name] = theme); + delete App.vars.cache.themeKeys; + return this; + }, + + /** + * Appends a placeholder to an element + * + * @param {string} src Placeholder URL string + * @param el A selector or a reference to a DOM node + */ + addImage: function(src, el) { + //todo: use jquery fallback if available for all QSA references + var nodes = DOM.getNodeArray(el); + nodes.forEach(function (node) { + var img = DOM.newEl('img'); + var domProps = {}; + domProps[App.setup.dataAttr] = src; + DOM.setAttr(img, domProps); + node.appendChild(img); + }); + return this; + }, + + /** + * Sets whether or not an image is updated on resize. + * If an image is set to be updated, it is immediately rendered. + * + * @param {Object} el Image DOM element + * @param {Boolean} value Resizable update flag value + */ + setResizeUpdate: function(el, value) { + if (el.holderData) { + el.holderData.resizeUpdate = !!value; + if (el.holderData.resizeUpdate) { + updateResizableElements(el); + } + } + }, + + /** + * Runs Holder with options. By default runs Holder on all images with "holder.js" in their source attributes. + * + * @param {Object} userOptions Options object, can contain domain, themes, images, and bgnodes properties + */ + run: function(userOptions) { + //todo: split processing into separate queues + userOptions = userOptions || {}; + var engineSettings = {}; + var options = extend(App.settings, userOptions); + + App.vars.preempted = true; + App.vars.dataAttr = options.dataAttr || App.setup.dataAttr; + + engineSettings.renderer = options.renderer ? options.renderer : App.setup.renderer; + if (App.setup.renderers.join(',').indexOf(engineSettings.renderer) === -1) { + engineSettings.renderer = App.setup.supportsSVG ? 'svg' : (App.setup.supportsCanvas ? 'canvas' : 'html'); + } + + var images = DOM.getNodeArray(options.images); + var bgnodes = DOM.getNodeArray(options.bgnodes); + var stylenodes = DOM.getNodeArray(options.stylenodes); + var objects = DOM.getNodeArray(options.objects); + + engineSettings.stylesheets = []; + engineSettings.svgXMLStylesheet = true; + engineSettings.noFontFallback = !!options.noFontFallback; + engineSettings.noBackgroundSize = !!options.noBackgroundSize; + + stylenodes.forEach(function (styleNode) { + if (styleNode.attributes.rel && styleNode.attributes.href && styleNode.attributes.rel.value == 'stylesheet') { + var href = styleNode.attributes.href.value; + //todo: write isomorphic relative-to-absolute URL function + var proxyLink = DOM.newEl('a'); + proxyLink.href = href; + var stylesheetURL = proxyLink.protocol + '//' + proxyLink.host + proxyLink.pathname + proxyLink.search; + engineSettings.stylesheets.push(stylesheetURL); + } + }); + + bgnodes.forEach(function (bgNode) { + //Skip processing background nodes if getComputedStyle is unavailable, since only modern browsers would be able to use canvas or SVG to render to background + if (!global.getComputedStyle) return; + var backgroundImage = global.getComputedStyle(bgNode, null).getPropertyValue('background-image'); + var dataBackgroundImage = bgNode.getAttribute('data-background-src'); + var rawURL = dataBackgroundImage || backgroundImage; + + var holderURL = null; + var holderString = options.domain + '/'; + var holderStringIndex = rawURL.indexOf(holderString); + + if (holderStringIndex === 0) { + holderURL = rawURL; + } else if (holderStringIndex === 1 && rawURL[0] === '?') { + holderURL = rawURL.slice(1); + } else { + var fragment = rawURL.substr(holderStringIndex).match(/([^\"]*)"?\)/); + if (fragment !== null) { + holderURL = fragment[1]; + } else if (rawURL.indexOf('url(') === 0) { + throw 'Holder: unable to parse background URL: ' + rawURL; + } + } + + if (holderURL) { + var holderFlags = parseURL(holderURL, options); + if (holderFlags) { + prepareDOMElement({ + mode: 'background', + el: bgNode, + flags: holderFlags, + engineSettings: engineSettings + }); + } + } + }); + + objects.forEach(function (object) { + var objectAttr = {}; + + try { + objectAttr.data = object.getAttribute('data'); + objectAttr.dataSrc = object.getAttribute(App.vars.dataAttr); + } catch (e) {} + + var objectHasSrcURL = objectAttr.data != null && objectAttr.data.indexOf(options.domain) === 0; + var objectHasDataSrcURL = objectAttr.dataSrc != null && objectAttr.dataSrc.indexOf(options.domain) === 0; + + if (objectHasSrcURL) { + prepareImageElement(options, engineSettings, objectAttr.data, object); + } else if (objectHasDataSrcURL) { + prepareImageElement(options, engineSettings, objectAttr.dataSrc, object); + } + }); + + images.forEach(function (image) { + var imageAttr = {}; + + try { + imageAttr.src = image.getAttribute('src'); + imageAttr.dataSrc = image.getAttribute(App.vars.dataAttr); + imageAttr.rendered = image.getAttribute('data-holder-rendered'); + } catch (e) {} + + var imageHasSrc = imageAttr.src != null; + var imageHasDataSrcURL = imageAttr.dataSrc != null && imageAttr.dataSrc.indexOf(options.domain) === 0; + var imageRendered = imageAttr.rendered != null && imageAttr.rendered == 'true'; + + if (imageHasSrc) { + if (imageAttr.src.indexOf(options.domain) === 0) { + prepareImageElement(options, engineSettings, imageAttr.src, image); + } else if (imageHasDataSrcURL) { + //Image has a valid data-src and an invalid src + if (imageRendered) { + //If the placeholder has already been render, re-render it + prepareImageElement(options, engineSettings, imageAttr.dataSrc, image); + } else { + //If the placeholder has not been rendered, check if the image exists and render a fallback if it doesn't + (function(src, options, engineSettings, dataSrc, image) { + utils.imageExists(src, function(exists) { + if (!exists) { + prepareImageElement(options, engineSettings, dataSrc, image); + } + }); + })(imageAttr.src, options, engineSettings, imageAttr.dataSrc, image); + } + } + } else if (imageHasDataSrcURL) { + prepareImageElement(options, engineSettings, imageAttr.dataSrc, image); + } + }); + + return this; + } + }; + + var App = { + settings: { + domain: 'holder.js', + images: 'img', + objects: 'object', + bgnodes: 'body .holderjs', + stylenodes: 'head link.holderjs', + themes: { + 'gray': { + bg: '#EEEEEE', + fg: '#AAAAAA' + }, + 'social': { + bg: '#3a5a97', + fg: '#FFFFFF' + }, + 'industrial': { + bg: '#434A52', + fg: '#C2F200' + }, + 'sky': { + bg: '#0D8FDB', + fg: '#FFFFFF' + }, + 'vine': { + bg: '#39DBAC', + fg: '#1E292C' + }, + 'lava': { + bg: '#F8591A', + fg: '#1C2846' + } + } + }, + defaults: { + size: 10, + units: 'pt', + scale: 1 / 16 + } + }; + + /** + * Processes provided source attribute and sets up the appropriate rendering workflow + * + * @private + * @param options Instance options from Holder.run + * @param renderSettings Instance configuration + * @param src Image URL + * @param el Image DOM element + */ + function prepareImageElement(options, engineSettings, src, el) { + var holderFlags = parseURL(src.substr(src.lastIndexOf(options.domain)), options); + if (holderFlags) { + prepareDOMElement({ + mode: null, + el: el, + flags: holderFlags, + engineSettings: engineSettings + }); + } + } + + /** + * Processes a Holder URL and extracts configuration from query string + * + * @private + * @param url URL + * @param instanceOptions Instance options from Holder.run + */ + function parseURL(url, instanceOptions) { + var holder = { + theme: extend(App.settings.themes.gray, null), + stylesheets: instanceOptions.stylesheets, + instanceOptions: instanceOptions + }; + + var firstQuestionMark = url.indexOf('?'); + var parts = [url]; + + if (firstQuestionMark !== -1) { + parts = [url.slice(0, firstQuestionMark), url.slice(firstQuestionMark + 1)]; + } + + var basics = parts[0].split('/'); + + holder.holderURL = url; + + var dimensions = basics[1]; + var dimensionData = dimensions.match(/([\d]+p?)x([\d]+p?)/); + + if (!dimensionData) return false; + + holder.fluid = dimensions.indexOf('p') !== -1; + + holder.dimensions = { + width: dimensionData[1].replace('p', '%'), + height: dimensionData[2].replace('p', '%') + }; + + if (parts.length === 2) { + var options = querystring.parse(parts[1]); + + // Dimensions + + if (utils.truthy(options.ratio)) { + holder.fluid = true; + var ratioWidth = parseFloat(holder.dimensions.width.replace('%', '')); + var ratioHeight = parseFloat(holder.dimensions.height.replace('%', '')); + + ratioHeight = Math.floor(100 * (ratioHeight / ratioWidth)); + ratioWidth = 100; + + holder.dimensions.width = ratioWidth + '%'; + holder.dimensions.height = ratioHeight + '%'; + } + + holder.auto = utils.truthy(options.auto); + + // Colors + + if (options.bg) { + holder.theme.bg = utils.parseColor(options.bg); + } + + if (options.fg) { + holder.theme.fg = utils.parseColor(options.fg); + } + + //todo: add automatic foreground to themes without foreground + if (options.bg && !options.fg) { + holder.autoFg = true; + } + + if (options.theme && holder.instanceOptions.themes.hasOwnProperty(options.theme)) { + holder.theme = extend(holder.instanceOptions.themes[options.theme], null); + } + + // Text + + if (options.text) { + holder.text = options.text; + } + + if (options.textmode) { + holder.textmode = options.textmode; + } + + if (options.size && parseFloat(options.size)) { + holder.size = parseFloat(options.size); + } + + if (options.font) { + holder.font = options.font; + } + + if (options.align) { + holder.align = options.align; + } + + if (options.lineWrap) { + holder.lineWrap = options.lineWrap; + } + + holder.nowrap = utils.truthy(options.nowrap); + + // Miscellaneous + + holder.outline = utils.truthy(options.outline); + + if (utils.truthy(options.random)) { + App.vars.cache.themeKeys = App.vars.cache.themeKeys || Object.keys(holder.instanceOptions.themes); + var _theme = App.vars.cache.themeKeys[0 | Math.random() * App.vars.cache.themeKeys.length]; + holder.theme = extend(holder.instanceOptions.themes[_theme], null); + } + } + + return holder; + } + + /** + * Modifies the DOM to fit placeholders and sets up resizable image callbacks (for fluid and automatically sized placeholders) + * + * @private + * @param settings DOM prep settings + */ + function prepareDOMElement(prepSettings) { + var mode = prepSettings.mode; + var el = prepSettings.el; + var flags = prepSettings.flags; + var _engineSettings = prepSettings.engineSettings; + var dimensions = flags.dimensions, + theme = flags.theme; + var dimensionsCaption = dimensions.width + 'x' + dimensions.height; + mode = mode == null ? (flags.fluid ? 'fluid' : 'image') : mode; + var holderTemplateRe = /holder_([a-z]+)/g; + var dimensionsInText = false; + + if (flags.text != null) { + theme.text = flags.text; + + //<object> SVG embedding doesn't parse Unicode properly + if (el.nodeName.toLowerCase() === 'object') { + var textLines = theme.text.split('\\n'); + for (var k = 0; k < textLines.length; k++) { + textLines[k] = utils.encodeHtmlEntity(textLines[k]); + } + theme.text = textLines.join('\\n'); + } + } + + if (theme.text) { + var holderTemplateMatches = theme.text.match(holderTemplateRe); + + if (holderTemplateMatches !== null) { + //todo: optimize template replacement + holderTemplateMatches.forEach(function (match) { + if (match === 'holder_dimensions') { + theme.text = theme.text.replace(match, dimensionsCaption); + } + }); + } + } + + var holderURL = flags.holderURL; + var engineSettings = extend(_engineSettings, null); + + if (flags.font) { + /* + If external fonts are used in a <img> placeholder rendered with SVG, Holder falls back to canvas. + + This is done because Firefox and Chrome disallow embedded SVGs from referencing external assets. + The workaround is either to change the placeholder tag from <img> to <object> or to use the canvas renderer. + */ + theme.font = flags.font; + if (!engineSettings.noFontFallback && el.nodeName.toLowerCase() === 'img' && App.setup.supportsCanvas && engineSettings.renderer === 'svg') { + engineSettings = extend(engineSettings, { + renderer: 'canvas' + }); + } + } + + //Chrome and Opera require a quick 10ms re-render if web fonts are used with canvas + if (flags.font && engineSettings.renderer == 'canvas') { + engineSettings.reRender = true; + } + + if (mode == 'background') { + if (el.getAttribute('data-background-src') == null) { + DOM.setAttr(el, { + 'data-background-src': holderURL + }); + } + } else { + var domProps = {}; + domProps[App.vars.dataAttr] = holderURL; + DOM.setAttr(el, domProps); + } + + flags.theme = theme; + + //todo consider using all renderSettings in holderData + el.holderData = { + flags: flags, + engineSettings: engineSettings + }; + + if (mode == 'image' || mode == 'fluid') { + DOM.setAttr(el, { + 'alt': theme.text ? (dimensionsInText ? theme.text : theme.text + ' [' + dimensionsCaption + ']') : dimensionsCaption + }); + } + + var renderSettings = { + mode: mode, + el: el, + holderSettings: { + dimensions: dimensions, + theme: theme, + flags: flags + }, + engineSettings: engineSettings + }; + + if (mode == 'image') { + if (!flags.auto) { + el.style.width = dimensions.width + 'px'; + el.style.height = dimensions.height + 'px'; + } + + if (engineSettings.renderer == 'html') { + el.style.backgroundColor = theme.bg; + } else { + render(renderSettings); + + if (flags.textmode == 'exact') { + el.holderData.resizeUpdate = true; + App.vars.resizableImages.push(el); + updateResizableElements(el); + } + } + } else if (mode == 'background' && engineSettings.renderer != 'html') { + render(renderSettings); + } else if (mode == 'fluid') { + el.holderData.resizeUpdate = true; + + if (dimensions.height.slice(-1) == '%') { + el.style.height = dimensions.height; + } else if (flags.auto == null || !flags.auto) { + el.style.height = dimensions.height + 'px'; + } + if (dimensions.width.slice(-1) == '%') { + el.style.width = dimensions.width; + } else if (flags.auto == null || !flags.auto) { + el.style.width = dimensions.width + 'px'; + } + if (el.style.display == 'inline' || el.style.display === '' || el.style.display == 'none') { + el.style.display = 'block'; + } + + setInitialDimensions(el); + + if (engineSettings.renderer == 'html') { + el.style.backgroundColor = theme.bg; + } else { + App.vars.resizableImages.push(el); + updateResizableElements(el); + } + } + } + + /** + * Core function that takes output from renderers and sets it as the source or background-image of the target element + * + * @private + * @param renderSettings Renderer settings + */ + function render(renderSettings) { + var image = null; + var mode = renderSettings.mode; + var el = renderSettings.el; + var holderSettings = renderSettings.holderSettings; + var engineSettings = renderSettings.engineSettings; + + switch (engineSettings.renderer) { + case 'svg': + if (!App.setup.supportsSVG) return; + break; + case 'canvas': + if (!App.setup.supportsCanvas) return; + break; + default: + return; + } + + //todo: move generation of scene up to flag generation to reduce extra object creation + var scene = { + width: holderSettings.dimensions.width, + height: holderSettings.dimensions.height, + theme: holderSettings.theme, + flags: holderSettings.flags + }; + + var sceneGraph = buildSceneGraph(scene); + + function getRenderedImage() { + var image = null; + switch (engineSettings.renderer) { + case 'canvas': + image = sgCanvasRenderer(sceneGraph, renderSettings); + break; + case 'svg': + image = svgRenderer(sceneGraph, renderSettings); + break; + default: + throw 'Holder: invalid renderer: ' + engineSettings.renderer; + } + + return image; + } + + image = getRenderedImage(); + + if (image == null) { + throw 'Holder: couldn\'t render placeholder'; + } + + //todo: add <object> canvas rendering + if (mode == 'background') { + el.style.backgroundImage = 'url(' + image + ')'; + + if (!engineSettings.noBackgroundSize) { + el.style.backgroundSize = scene.width + 'px ' + scene.height + 'px'; + } + } else { + if (el.nodeName.toLowerCase() === 'img') { + DOM.setAttr(el, { + 'src': image + }); + } else if (el.nodeName.toLowerCase() === 'object') { + DOM.setAttr(el, { + 'data': image, + 'type': 'image/svg+xml' + }); + } + if (engineSettings.reRender) { + global.setTimeout(function () { + var image = getRenderedImage(); + if (image == null) { + throw 'Holder: couldn\'t render placeholder'; + } + //todo: refactor this code into a function + if (el.nodeName.toLowerCase() === 'img') { + DOM.setAttr(el, { + 'src': image + }); + } else if (el.nodeName.toLowerCase() === 'object') { + DOM.setAttr(el, { + 'data': image, + 'type': 'image/svg+xml' + }); + } + }, 150); + } + } + //todo: account for re-rendering + DOM.setAttr(el, { + 'data-holder-rendered': true + }); + } + + /** + * Core function that takes a Holder scene description and builds a scene graph + * + * @private + * @param scene Holder scene object + */ + //todo: make this function reusable + //todo: merge app defaults and setup properties into the scene argument + function buildSceneGraph(scene) { + var fontSize = App.defaults.size; + if (parseFloat(scene.theme.size)) { + fontSize = scene.theme.size; + } else if (parseFloat(scene.flags.size)) { + fontSize = scene.flags.size; + } + + scene.font = { + family: scene.theme.font ? scene.theme.font : 'Arial, Helvetica, Open Sans, sans-serif', + size: textSize(scene.width, scene.height, fontSize, App.defaults.scale), + units: scene.theme.units ? scene.theme.units : App.defaults.units, + weight: scene.theme.fontweight ? scene.theme.fontweight : 'bold' + }; + + scene.text = scene.theme.text || Math.floor(scene.width) + 'x' + Math.floor(scene.height); + + scene.noWrap = scene.theme.nowrap || scene.flags.nowrap; + + scene.align = scene.theme.align || scene.flags.align || 'center'; + + switch (scene.flags.textmode) { + case 'literal': + scene.text = scene.flags.dimensions.width + 'x' + scene.flags.dimensions.height; + break; + case 'exact': + if (!scene.flags.exactDimensions) break; + scene.text = Math.floor(scene.flags.exactDimensions.width) + 'x' + Math.floor(scene.flags.exactDimensions.height); + break; + } + + var lineWrap = scene.flags.lineWrap || App.setup.lineWrapRatio; + var sceneMargin = scene.width * lineWrap; + var maxLineWidth = sceneMargin; + + var sceneGraph = new SceneGraph({ + width: scene.width, + height: scene.height + }); + + var Shape = sceneGraph.Shape; + + var holderBg = new Shape.Rect('holderBg', { + fill: scene.theme.bg + }); + + holderBg.resize(scene.width, scene.height); + sceneGraph.root.add(holderBg); + + if (scene.flags.outline) { + var outlineColor = new Color(holderBg.properties.fill); + outlineColor = outlineColor.lighten(outlineColor.lighterThan('7f7f7f') ? -0.1 : 0.1); + holderBg.properties.outline = { + fill: outlineColor.toHex(true), + width: 2 + }; + } + + var holderTextColor = scene.theme.fg; + + if (scene.flags.autoFg) { + var holderBgColor = new Color(holderBg.properties.fill); + var lightColor = new Color('fff'); + var darkColor = new Color('000', { + 'alpha': 0.285714 + }); + + holderTextColor = holderBgColor.blendAlpha(holderBgColor.lighterThan('7f7f7f') ? darkColor : lightColor).toHex(true); + } + + var holderTextGroup = new Shape.Group('holderTextGroup', { + text: scene.text, + align: scene.align, + font: scene.font, + fill: holderTextColor + }); + + holderTextGroup.moveTo(null, null, 1); + sceneGraph.root.add(holderTextGroup); + + var tpdata = holderTextGroup.textPositionData = stagingRenderer(sceneGraph); + if (!tpdata) { + throw 'Holder: staging fallback not supported yet.'; + } + holderTextGroup.properties.leading = tpdata.boundingBox.height; + + var textNode = null; + var line = null; + + function finalizeLine(parent, line, width, height) { + line.width = width; + line.height = height; + parent.width = Math.max(parent.width, line.width); + parent.height += line.height; + } + + if (tpdata.lineCount > 1) { + var offsetX = 0; + var offsetY = 0; + var lineIndex = 0; + var lineKey; + line = new Shape.Group('line' + lineIndex); + + //Double margin so that left/right-aligned next is not flush with edge of image + if (scene.align === 'left' || scene.align === 'right') { + maxLineWidth = scene.width * (1 - (1 - lineWrap) * 2); + } + + for (var i = 0; i < tpdata.words.length; i++) { + var word = tpdata.words[i]; + textNode = new Shape.Text(word.text); + var newline = word.text == '\\n'; + if (!scene.noWrap && (offsetX + word.width >= maxLineWidth || newline === true)) { + finalizeLine(holderTextGroup, line, offsetX, holderTextGroup.properties.leading); + holderTextGroup.add(line); + offsetX = 0; + offsetY += holderTextGroup.properties.leading; + lineIndex += 1; + line = new Shape.Group('line' + lineIndex); + line.y = offsetY; + } + if (newline === true) { + continue; + } + textNode.moveTo(offsetX, 0); + offsetX += tpdata.spaceWidth + word.width; + line.add(textNode); + } + + finalizeLine(holderTextGroup, line, offsetX, holderTextGroup.properties.leading); + holderTextGroup.add(line); + + if (scene.align === 'left') { + holderTextGroup.moveTo(scene.width - sceneMargin, null, null); + } else if (scene.align === 'right') { + for (lineKey in holderTextGroup.children) { + line = holderTextGroup.children[lineKey]; + line.moveTo(scene.width - line.width, null, null); + } + + holderTextGroup.moveTo(0 - (scene.width - sceneMargin), null, null); + } else { + for (lineKey in holderTextGroup.children) { + line = holderTextGroup.children[lineKey]; + line.moveTo((holderTextGroup.width - line.width) / 2, null, null); + } + + holderTextGroup.moveTo((scene.width - holderTextGroup.width) / 2, null, null); + } + + holderTextGroup.moveTo(null, (scene.height - holderTextGroup.height) / 2, null); + + //If the text exceeds vertical space, move it down so the first line is visible + if ((scene.height - holderTextGroup.height) / 2 < 0) { + holderTextGroup.moveTo(null, 0, null); + } + } else { + textNode = new Shape.Text(scene.text); + line = new Shape.Group('line0'); + line.add(textNode); + holderTextGroup.add(line); + + if (scene.align === 'left') { + holderTextGroup.moveTo(scene.width - sceneMargin, null, null); + } else if (scene.align === 'right') { + holderTextGroup.moveTo(0 - (scene.width - sceneMargin), null, null); + } else { + holderTextGroup.moveTo((scene.width - tpdata.boundingBox.width) / 2, null, null); + } + + holderTextGroup.moveTo(null, (scene.height - tpdata.boundingBox.height) / 2, null); + } + + //todo: renderlist + return sceneGraph; + } + + /** + * Adaptive text sizing function + * + * @private + * @param width Parent width + * @param height Parent height + * @param fontSize Requested text size + * @param scale Proportional scale of text + */ + function textSize(width, height, fontSize, scale) { + var stageWidth = parseInt(width, 10); + var stageHeight = parseInt(height, 10); + + var bigSide = Math.max(stageWidth, stageHeight); + var smallSide = Math.min(stageWidth, stageHeight); + + var newHeight = 0.8 * Math.min(smallSide, bigSide * scale); + return Math.round(Math.max(fontSize, newHeight)); + } + + /** + * Iterates over resizable (fluid or auto) placeholders and renders them + * + * @private + * @param element Optional element selector, specified only if a specific element needs to be re-rendered + */ + function updateResizableElements(element) { + var images; + if (element == null || element.nodeType == null) { + images = App.vars.resizableImages; + } else { + images = [element]; + } + for (var i = 0, l = images.length; i < l; i++) { + var el = images[i]; + if (el.holderData) { + var flags = el.holderData.flags; + var dimensions = dimensionCheck(el); + if (dimensions) { + if (!el.holderData.resizeUpdate) { + continue; + } + + if (flags.fluid && flags.auto) { + var fluidConfig = el.holderData.fluidConfig; + switch (fluidConfig.mode) { + case 'width': + dimensions.height = dimensions.width / fluidConfig.ratio; + break; + case 'height': + dimensions.width = dimensions.height * fluidConfig.ratio; + break; + } + } + + var settings = { + mode: 'image', + holderSettings: { + dimensions: dimensions, + theme: flags.theme, + flags: flags + }, + el: el, + engineSettings: el.holderData.engineSettings + }; + + if (flags.textmode == 'exact') { + flags.exactDimensions = dimensions; + settings.holderSettings.dimensions = flags.dimensions; + } + + render(settings); + } else { + setInvisible(el); + } + } + } + } + + /** + * Sets up aspect ratio metadata for fluid placeholders, in order to preserve proportions when resizing + * + * @private + * @param el Image DOM element + */ + function setInitialDimensions(el) { + if (el.holderData) { + var dimensions = dimensionCheck(el); + if (dimensions) { + var flags = el.holderData.flags; + + var fluidConfig = { + fluidHeight: flags.dimensions.height.slice(-1) == '%', + fluidWidth: flags.dimensions.width.slice(-1) == '%', + mode: null, + initialDimensions: dimensions + }; + + if (fluidConfig.fluidWidth && !fluidConfig.fluidHeight) { + fluidConfig.mode = 'width'; + fluidConfig.ratio = fluidConfig.initialDimensions.width / parseFloat(flags.dimensions.height); + } else if (!fluidConfig.fluidWidth && fluidConfig.fluidHeight) { + fluidConfig.mode = 'height'; + fluidConfig.ratio = parseFloat(flags.dimensions.width) / fluidConfig.initialDimensions.height; + } + + el.holderData.fluidConfig = fluidConfig; + } else { + setInvisible(el); + } + } + } + + /** + * Iterates through all current invisible images, and if they're visible, renders them and removes them from further checks. Runs every animation frame. + * + * @private + */ + function visibilityCheck() { + var renderableImages = []; + var keys = Object.keys(App.vars.invisibleImages); + var el; + + keys.forEach(function (key) { + el = App.vars.invisibleImages[key]; + if (dimensionCheck(el) && el.nodeName.toLowerCase() == 'img') { + renderableImages.push(el); + delete App.vars.invisibleImages[key]; + } + }); + + if (renderableImages.length) { + Holder.run({ + images: renderableImages + }); + } + + // Done to prevent 100% CPU usage via aggressive calling of requestAnimationFrame + setTimeout(function () { + global.requestAnimationFrame(visibilityCheck); + }, 10); + } + + /** + * Starts checking for invisible placeholders if not doing so yet. Does nothing otherwise. + * + * @private + */ + function startVisibilityCheck() { + if (!App.vars.visibilityCheckStarted) { + global.requestAnimationFrame(visibilityCheck); + App.vars.visibilityCheckStarted = true; + } + } + + /** + * Sets a unique ID for an image detected to be invisible and adds it to the map of invisible images checked by visibilityCheck + * + * @private + * @param el Invisible DOM element + */ + function setInvisible(el) { + if (!el.holderData.invisibleId) { + App.vars.invisibleId += 1; + App.vars.invisibleImages['i' + App.vars.invisibleId] = el; + el.holderData.invisibleId = App.vars.invisibleId; + } + } + + //todo: see if possible to convert stagingRenderer to use HTML only + var stagingRenderer = (function() { + var svg = null, + stagingText = null, + stagingTextNode = null; + return function(graph) { + var rootNode = graph.root; + if (App.setup.supportsSVG) { + var firstTimeSetup = false; + var tnode = function(text) { + return document.createTextNode(text); + }; + if (svg == null || svg.parentNode !== document.body) { + firstTimeSetup = true; + } + + svg = SVG.initSVG(svg, rootNode.properties.width, rootNode.properties.height); + //Show staging element before staging + svg.style.display = 'block'; + + if (firstTimeSetup) { + stagingText = DOM.newEl('text', SVG_NS); + stagingTextNode = tnode(null); + DOM.setAttr(stagingText, { + x: 0 + }); + stagingText.appendChild(stagingTextNode); + svg.appendChild(stagingText); + document.body.appendChild(svg); + svg.style.visibility = 'hidden'; + svg.style.position = 'absolute'; + svg.style.top = '-100%'; + svg.style.left = '-100%'; + //todo: workaround for zero-dimension <svg> tag in Opera 12 + //svg.setAttribute('width', 0); + //svg.setAttribute('height', 0); + } + + var holderTextGroup = rootNode.children.holderTextGroup; + var htgProps = holderTextGroup.properties; + DOM.setAttr(stagingText, { + 'y': htgProps.font.size, + 'style': utils.cssProps({ + 'font-weight': htgProps.font.weight, + 'font-size': htgProps.font.size + htgProps.font.units, + 'font-family': htgProps.font.family + }) + }); + + //Unescape HTML entities to get approximately the right width + var txt = DOM.newEl('textarea'); + txt.innerHTML = htgProps.text; + stagingTextNode.nodeValue = txt.value; + + //Get bounding box for the whole string (total width and height) + var stagingTextBBox = stagingText.getBBox(); + + //Get line count and split the string into words + var lineCount = Math.ceil(stagingTextBBox.width / rootNode.properties.width); + var words = htgProps.text.split(' '); + var newlines = htgProps.text.match(/\\n/g); + lineCount += newlines == null ? 0 : newlines.length; + + //Get bounding box for the string with spaces removed + stagingTextNode.nodeValue = htgProps.text.replace(/[ ]+/g, ''); + var computedNoSpaceLength = stagingText.getComputedTextLength(); + + //Compute average space width + var diffLength = stagingTextBBox.width - computedNoSpaceLength; + var spaceWidth = Math.round(diffLength / Math.max(1, words.length - 1)); + + //Get widths for every word with space only if there is more than one line + var wordWidths = []; + if (lineCount > 1) { + stagingTextNode.nodeValue = ''; + for (var i = 0; i < words.length; i++) { + if (words[i].length === 0) continue; + stagingTextNode.nodeValue = utils.decodeHtmlEntity(words[i]); + var bbox = stagingText.getBBox(); + wordWidths.push({ + text: words[i], + width: bbox.width + }); + } + } + + //Hide staging element after staging + svg.style.display = 'none'; + + return { + spaceWidth: spaceWidth, + lineCount: lineCount, + boundingBox: stagingTextBBox, + words: wordWidths + }; + } else { + //todo: canvas fallback for measuring text on android 2.3 + return false; + } + }; + })(); + + //Helpers + + /** + * Prevents a function from being called too often, waits until a timer elapses to call it again + * + * @param fn Function to call + */ + function debounce(fn) { + if (!App.vars.debounceTimer) fn.call(this); + if (App.vars.debounceTimer) global.clearTimeout(App.vars.debounceTimer); + App.vars.debounceTimer = global.setTimeout(function() { + App.vars.debounceTimer = null; + fn.call(this); + }, App.setup.debounce); + } + + /** + * Holder-specific resize/orientation change callback, debounced to prevent excessive execution + */ + function resizeEvent() { + debounce(function() { + updateResizableElements(null); + }); + } + + //Set up flags + + for (var flag in App.flags) { + if (!App.flags.hasOwnProperty(flag)) continue; + App.flags[flag].match = function(val) { + return val.match(this.regex); + }; + } + + //Properties set once on setup + + App.setup = { + renderer: 'html', + debounce: 100, + ratio: 1, + supportsCanvas: false, + supportsSVG: false, + lineWrapRatio: 0.9, + dataAttr: 'data-src', + renderers: ['html', 'canvas', 'svg'] + }; + + //Properties modified during runtime + + App.vars = { + preempted: false, + resizableImages: [], + invisibleImages: {}, + invisibleId: 0, + visibilityCheckStarted: false, + debounceTimer: null, + cache: {} + }; + + //Pre-flight + + (function() { + var canvas = DOM.newEl('canvas'); + + if (canvas.getContext) { + if (canvas.toDataURL('image/png').indexOf('data:image/png') != -1) { + App.setup.renderer = 'canvas'; + App.setup.supportsCanvas = true; + } + } + + if (!!document.createElementNS && !!document.createElementNS(SVG_NS, 'svg').createSVGRect) { + App.setup.renderer = 'svg'; + App.setup.supportsSVG = true; + } + })(); + + //Starts checking for invisible placeholders + startVisibilityCheck(); + + if (onDomReady) { + onDomReady(function() { + if (!App.vars.preempted) { + Holder.run(); + } + if (global.addEventListener) { + global.addEventListener('resize', resizeEvent, false); + global.addEventListener('orientationchange', resizeEvent, false); + } else { + global.attachEvent('onresize', resizeEvent); + } + + if (typeof global.Turbolinks == 'object') { + global.document.addEventListener('page:change', function() { + Holder.run(); + }); + } + }); + } + + module.exports = Holder; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + + /*! + * onDomReady.js 1.4.0 (c) 2013 Tubal Martin - MIT license + * + * Specially modified to work with Holder.js + */ + + function _onDomReady(win) { + //Lazy loading fix for Firefox < 3.6 + //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html + if (document.readyState == null && document.addEventListener) { + document.addEventListener("DOMContentLoaded", function DOMContentLoaded() { + document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); + document.readyState = "complete"; + }, false); + document.readyState = "loading"; + } + + var doc = win.document, + docElem = doc.documentElement, + + LOAD = "load", + FALSE = false, + ONLOAD = "on"+LOAD, + COMPLETE = "complete", + READYSTATE = "readyState", + ATTACHEVENT = "attachEvent", + DETACHEVENT = "detachEvent", + ADDEVENTLISTENER = "addEventListener", + DOMCONTENTLOADED = "DOMContentLoaded", + ONREADYSTATECHANGE = "onreadystatechange", + REMOVEEVENTLISTENER = "removeEventListener", + + // W3C Event model + w3c = ADDEVENTLISTENER in doc, + _top = FALSE, + + // isReady: Is the DOM ready to be used? Set to true once it occurs. + isReady = FALSE, + + // Callbacks pending execution until DOM is ready + callbacks = []; + + // Handle when the DOM is ready + function ready( fn ) { + if ( !isReady ) { + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !doc.body ) { + return defer( ready ); + } + + // Remember that the DOM is ready + isReady = true; + + // Execute all callbacks + while ( fn = callbacks.shift() ) { + defer( fn ); + } + } + } + + // The ready event handler + function completed( event ) { + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( w3c || event.type === LOAD || doc[READYSTATE] === COMPLETE ) { + detach(); + ready(); + } + } + + // Clean-up method for dom ready events + function detach() { + if ( w3c ) { + doc[REMOVEEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE ); + win[REMOVEEVENTLISTENER]( LOAD, completed, FALSE ); + } else { + doc[DETACHEVENT]( ONREADYSTATECHANGE, completed ); + win[DETACHEVENT]( ONLOAD, completed ); + } + } + + // Defers a function, scheduling it to run after the current call stack has cleared. + function defer( fn, wait ) { + // Allow 0 to be passed + setTimeout( fn, +wait >= 0 ? wait : 1 ); + } + + // Attach the listeners: + + // Catch cases where onDomReady is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( doc[READYSTATE] === COMPLETE ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + defer( ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( w3c ) { + // Use the handy event callback + doc[ADDEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE ); + + // A fallback to window.onload, that will always work + win[ADDEVENTLISTENER]( LOAD, completed, FALSE ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + doc[ATTACHEVENT]( ONREADYSTATECHANGE, completed ); + + // A fallback to window.onload, that will always work + win[ATTACHEVENT]( ONLOAD, completed ); + + // If IE and not a frame + // continually check to see if the document is ready + try { + _top = win.frameElement == null && docElem; + } catch(e) {} + + if ( _top && _top.doScroll ) { + (function doScrollCheck() { + if ( !isReady ) { + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + _top.doScroll("left"); + } catch(e) { + return defer( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + ready(); + } + })(); + } + } + + function onDomReady( fn ) { + // If DOM is ready, execute the function (async), otherwise wait + isReady ? defer( fn ) : callbacks.push( fn ); + } + + // Add version + onDomReady.version = "1.4.0"; + // Add method to check if DOM is ready + onDomReady.isReady = function(){ + return isReady; + }; + + return onDomReady; + } + + module.exports = typeof window !== "undefined" && _onDomReady(window); + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + + //Modified version of component/querystring + //Changes: updated dependencies, dot notation parsing, JSHint fixes + //Fork at https://github.com/imsky/querystring + + /** + * Module dependencies. + */ + + var encode = encodeURIComponent; + var decode = decodeURIComponent; + var trim = __webpack_require__(4); + var type = __webpack_require__(5); + + var arrayRegex = /(\w+)\[(\d+)\]/; + var objectRegex = /\w+\.\w+/; + + /** + * Parse the given query `str`. + * + * @param {String} str + * @return {Object} + * @api public + */ + + exports.parse = function(str){ + if ('string' !== typeof str) return {}; + + str = trim(str); + if ('' === str) return {}; + if ('?' === str.charAt(0)) str = str.slice(1); + + var obj = {}; + var pairs = str.split('&'); + for (var i = 0; i < pairs.length; i++) { + var parts = pairs[i].split('='); + var key = decode(parts[0]); + var m, ctx, prop; + + if (m = arrayRegex.exec(key)) { + obj[m[1]] = obj[m[1]] || []; + obj[m[1]][m[2]] = decode(parts[1]); + continue; + } + + if (m = objectRegex.test(key)) { + m = key.split('.'); + ctx = obj; + + while (m.length) { + prop = m.shift(); + + if (!prop.length) continue; + + if (!ctx[prop]) { + ctx[prop] = {}; + } else if (ctx[prop] && typeof ctx[prop] !== 'object') { + break; + } + + if (!m.length) { + ctx[prop] = decode(parts[1]); + } + + ctx = ctx[prop]; + } + + continue; + } + + obj[parts[0]] = null == parts[1] ? '' : decode(parts[1]); + } + + return obj; + }; + + /** + * Stringify the given `obj`. + * + * @param {Object} obj + * @return {String} + * @api public + */ + + exports.stringify = function(obj){ + if (!obj) return ''; + var pairs = []; + + for (var key in obj) { + var value = obj[key]; + + if ('array' == type(value)) { + for (var i = 0; i < value.length; ++i) { + pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i])); + } + continue; + } + + pairs.push(encode(key) + '=' + encode(obj[key])); + } + + return pairs.join('&'); + }; + + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + + + exports = module.exports = trim; + + function trim(str){ + return str.replace(/^\s*|\s*$/g, ''); + } + + exports.left = function(str){ + return str.replace(/^\s*/, ''); + }; + + exports.right = function(str){ + return str.replace(/\s*$/, ''); + }; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports) { + + /** + * toString ref. + */ + + var toString = Object.prototype.toString; + + /** + * Return the type of `val`. + * + * @param {Mixed} val + * @return {String} + * @api public + */ + + module.exports = function(val){ + switch (toString.call(val)) { + case '[object Date]': return 'date'; + case '[object RegExp]': return 'regexp'; + case '[object Arguments]': return 'arguments'; + case '[object Array]': return 'array'; + case '[object Error]': return 'error'; + } + + if (val === null) return 'null'; + if (val === undefined) return 'undefined'; + if (val !== val) return 'nan'; + if (val && val.nodeType === 1) return 'element'; + + if (isBuffer(val)) return 'buffer'; + + val = val.valueOf + ? val.valueOf() + : Object.prototype.valueOf.apply(val); + + return typeof val; + }; + + // code borrowed from https://github.com/feross/is-buffer/blob/master/index.js + function isBuffer(obj) { + return !!(obj != null && + (obj._isBuffer || // For Safari 5-7 (missing Object.prototype.constructor) + (obj.constructor && + typeof obj.constructor.isBuffer === 'function' && + obj.constructor.isBuffer(obj)) + )) + } + + +/***/ }), +/* 6 */ +/***/ (function(module, exports) { + + var SceneGraph = function(sceneProperties) { + var nodeCount = 1; + + //todo: move merge to helpers section + function merge(parent, child) { + for (var prop in child) { + parent[prop] = child[prop]; + } + return parent; + } + + var SceneNode = function(name) { + nodeCount++; + this.parent = null; + this.children = {}; + this.id = nodeCount; + this.name = 'n' + nodeCount; + if (typeof name !== 'undefined') { + this.name = name; + } + this.x = this.y = this.z = 0; + this.width = this.height = 0; + }; + + SceneNode.prototype.resize = function(width, height) { + if (width != null) { + this.width = width; + } + if (height != null) { + this.height = height; + } + }; + + SceneNode.prototype.moveTo = function(x, y, z) { + this.x = x != null ? x : this.x; + this.y = y != null ? y : this.y; + this.z = z != null ? z : this.z; + }; + + SceneNode.prototype.add = function(child) { + var name = child.name; + if (typeof this.children[name] === 'undefined') { + this.children[name] = child; + child.parent = this; + } else { + throw 'SceneGraph: child already exists: ' + name; + } + }; + + var RootNode = function() { + SceneNode.call(this, 'root'); + this.properties = sceneProperties; + }; + + RootNode.prototype = new SceneNode(); + + var Shape = function(name, props) { + SceneNode.call(this, name); + this.properties = { + 'fill': '#000000' + }; + if (typeof props !== 'undefined') { + merge(this.properties, props); + } else if (typeof name !== 'undefined' && typeof name !== 'string') { + throw 'SceneGraph: invalid node name'; + } + }; + + Shape.prototype = new SceneNode(); + + var Group = function() { + Shape.apply(this, arguments); + this.type = 'group'; + }; + + Group.prototype = new Shape(); + + var Rect = function() { + Shape.apply(this, arguments); + this.type = 'rect'; + }; + + Rect.prototype = new Shape(); + + var Text = function(text) { + Shape.call(this); + this.type = 'text'; + this.properties.text = text; + }; + + Text.prototype = new Shape(); + + var root = new RootNode(); + + this.Shape = { + 'Rect': Rect, + 'Text': Text, + 'Group': Group + }; + + this.root = root; + return this; + }; + + module.exports = SceneGraph; + + +/***/ }), +/* 7 */ +/***/ (function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Shallow object clone and merge + * + * @param a Object A + * @param b Object B + * @returns {Object} New object with all of A's properties, and all of B's properties, overwriting A's properties + */ + exports.extend = function(a, b) { + var c = {}; + for (var x in a) { + if (a.hasOwnProperty(x)) { + c[x] = a[x]; + } + } + if (b != null) { + for (var y in b) { + if (b.hasOwnProperty(y)) { + c[y] = b[y]; + } + } + } + return c; + }; + + /** + * Takes a k/v list of CSS properties and returns a rule + * + * @param props CSS properties object + */ + exports.cssProps = function(props) { + var ret = []; + for (var p in props) { + if (props.hasOwnProperty(p)) { + ret.push(p + ':' + props[p]); + } + } + return ret.join(';'); + }; + + /** + * Encodes HTML entities in a string + * + * @param str Input string + */ + exports.encodeHtmlEntity = function(str) { + var buf = []; + var charCode = 0; + for (var i = str.length - 1; i >= 0; i--) { + charCode = str.charCodeAt(i); + if (charCode > 128) { + buf.unshift(['&#', charCode, ';'].join('')); + } else { + buf.unshift(str[i]); + } + } + return buf.join(''); + }; + + /** + * Checks if an image exists + * + * @param src URL of image + * @param callback Callback to call once image status has been found + */ + exports.imageExists = function(src, callback) { + var image = new Image(); + image.onerror = function() { + callback.call(this, false); + }; + image.onload = function() { + callback.call(this, true); + }; + image.src = src; + }; + + /** + * Decodes HTML entities in a string + * + * @param str Input string + */ + exports.decodeHtmlEntity = function(str) { + return str.replace(/&#(\d+);/g, function(match, dec) { + return String.fromCharCode(dec); + }); + }; + + + /** + * Returns an element's dimensions if it's visible, `false` otherwise. + * + * @param el DOM element + */ + exports.dimensionCheck = function(el) { + var dimensions = { + height: el.clientHeight, + width: el.clientWidth + }; + + if (dimensions.height && dimensions.width) { + return dimensions; + } else { + return false; + } + }; + + + /** + * Returns true if value is truthy or if it is "semantically truthy" + * @param val + */ + exports.truthy = function(val) { + if (typeof val === 'string') { + return val === 'true' || val === 'yes' || val === '1' || val === 'on' || val === '✓'; + } + return !!val; + }; + + /** + * Parses input into a well-formed CSS color + * @param val + */ + exports.parseColor = function(val) { + var hexre = /(^(?:#?)[0-9a-f]{6}$)|(^(?:#?)[0-9a-f]{3}$)/i; + var rgbre = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/; + var rgbare = /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0\.\d{1,}|1)\)$/; + + var match = val.match(hexre); + var retval; + + if (match !== null) { + retval = match[1] || match[2]; + if (retval[0] !== '#') { + return '#' + retval; + } else { + return retval; + } + } + + match = val.match(rgbre); + + if (match !== null) { + retval = 'rgb(' + match.slice(1).join(',') + ')'; + return retval; + } + + match = val.match(rgbare); + + if (match !== null) { + retval = 'rgba(' + match.slice(1).join(',') + ')'; + return retval; + } + + return null; + }; + + /** + * Provides the correct scaling ratio for canvas drawing operations on HiDPI screens (e.g. Retina displays) + */ + exports.canvasRatio = function () { + var devicePixelRatio = 1; + var backingStoreRatio = 1; + + if (global.document) { + var canvas = global.document.createElement('canvas'); + if (canvas.getContext) { + var ctx = canvas.getContext('2d'); + devicePixelRatio = global.devicePixelRatio || 1; + backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; + } + } + + return devicePixelRatio / backingStoreRatio; + }; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {var DOM = __webpack_require__(9); + + var SVG_NS = 'http://www.w3.org/2000/svg'; + var NODE_TYPE_COMMENT = 8; + + /** + * Generic SVG element creation function + * + * @param svg SVG context, set to null if new + * @param width Document width + * @param height Document height + */ + exports.initSVG = function(svg, width, height) { + var defs, style, initialize = false; + + if (svg && svg.querySelector) { + style = svg.querySelector('style'); + if (style === null) { + initialize = true; + } + } else { + svg = DOM.newEl('svg', SVG_NS); + initialize = true; + } + + if (initialize) { + defs = DOM.newEl('defs', SVG_NS); + style = DOM.newEl('style', SVG_NS); + DOM.setAttr(style, { + 'type': 'text/css' + }); + defs.appendChild(style); + svg.appendChild(defs); + } + + //IE throws an exception if this is set and Chrome requires it to be set + if (svg.webkitMatchesSelector) { + svg.setAttribute('xmlns', SVG_NS); + } + + //Remove comment nodes + for (var i = 0; i < svg.childNodes.length; i++) { + if (svg.childNodes[i].nodeType === NODE_TYPE_COMMENT) { + svg.removeChild(svg.childNodes[i]); + } + } + + //Remove CSS + while (style.childNodes.length) { + style.removeChild(style.childNodes[0]); + } + + DOM.setAttr(svg, { + 'width': width, + 'height': height, + 'viewBox': '0 0 ' + width + ' ' + height, + 'preserveAspectRatio': 'none' + }); + + return svg; + }; + + /** + * Converts serialized SVG to a string suitable for data URI use + * @param svgString Serialized SVG string + * @param [base64] Use base64 encoding for data URI + */ + exports.svgStringToDataURI = function() { + var rawPrefix = 'data:image/svg+xml;charset=UTF-8,'; + var base64Prefix = 'data:image/svg+xml;charset=UTF-8;base64,'; + + return function(svgString, base64) { + if (base64) { + return base64Prefix + btoa(global.unescape(encodeURIComponent(svgString))); + } else { + return rawPrefix + encodeURIComponent(svgString); + } + }; + }(); + + /** + * Returns serialized SVG with XML processing instructions + * + * @param svg SVG context + * @param stylesheets CSS stylesheets to include + */ + exports.serializeSVG = function(svg, engineSettings) { + if (!global.XMLSerializer) return; + var serializer = new XMLSerializer(); + var svgCSS = ''; + var stylesheets = engineSettings.stylesheets; + + //External stylesheets: Processing Instruction method + if (engineSettings.svgXMLStylesheet) { + var xml = DOM.createXML(); + //Add <?xml-stylesheet ?> directives + for (var i = stylesheets.length - 1; i >= 0; i--) { + var csspi = xml.createProcessingInstruction('xml-stylesheet', 'href="' + stylesheets[i] + '" rel="stylesheet"'); + xml.insertBefore(csspi, xml.firstChild); + } + + xml.removeChild(xml.documentElement); + svgCSS = serializer.serializeToString(xml); + } + + var svgText = serializer.serializeToString(svg); + svgText = svgText.replace(/\&(\#[0-9]{2,}\;)/g, '&$1'); + return svgCSS + svgText; + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }), +/* 9 */ +/***/ (function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Generic new DOM element function + * + * @param tag Tag to create + * @param namespace Optional namespace value + */ + exports.newEl = function(tag, namespace) { + if (!global.document) return; + + if (namespace == null) { + return global.document.createElement(tag); + } else { + return global.document.createElementNS(namespace, tag); + } + }; + + /** + * Generic setAttribute function + * + * @param el Reference to DOM element + * @param attrs Object with attribute keys and values + */ + exports.setAttr = function (el, attrs) { + for (var a in attrs) { + el.setAttribute(a, attrs[a]); + } + }; + + /** + * Creates a XML document + * @private + */ + exports.createXML = function() { + if (!global.DOMParser) return; + return new DOMParser().parseFromString('<xml />', 'application/xml'); + }; + + /** + * Converts a value into an array of DOM nodes + * + * @param val A string, a NodeList, a Node, or an HTMLCollection + */ + exports.getNodeArray = function(val) { + var retval = null; + if (typeof(val) == 'string') { + retval = document.querySelectorAll(val); + } else if (global.NodeList && val instanceof global.NodeList) { + retval = val; + } else if (global.Node && val instanceof global.Node) { + retval = [val]; + } else if (global.HTMLCollection && val instanceof global.HTMLCollection) { + retval = val; + } else if (val instanceof Array) { + retval = val; + } else if (val === null) { + retval = []; + } + + retval = Array.prototype.slice.call(retval); + + return retval; + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }), +/* 10 */ +/***/ (function(module, exports) { + + var Color = function(color, options) { + //todo: support rgba, hsla, and rrggbbaa notation + //todo: use CIELAB internally + //todo: add clamp function (with sign) + if (typeof color !== 'string') return; + + this.original = color; + + if (color.charAt(0) === '#') { + color = color.slice(1); + } + + if (/[^a-f0-9]+/i.test(color)) return; + + if (color.length === 3) { + color = color.replace(/./g, '$&$&'); + } + + if (color.length !== 6) return; + + this.alpha = 1; + + if (options && options.alpha) { + this.alpha = options.alpha; + } + + this.set(parseInt(color, 16)); + }; + + //todo: jsdocs + Color.rgb2hex = function(r, g, b) { + function format (decimal) { + var hex = (decimal | 0).toString(16); + if (decimal < 16) { + hex = '0' + hex; + } + return hex; + } + + return [r, g, b].map(format).join(''); + }; + + //todo: jsdocs + Color.hsl2rgb = function (h, s, l) { + var H = h / 60; + var C = (1 - Math.abs(2 * l - 1)) * s; + var X = C * (1 - Math.abs(parseInt(H) % 2 - 1)); + var m = l - (C / 2); + + var r = 0, g = 0, b = 0; + + if (H >= 0 && H < 1) { + r = C; + g = X; + } else if (H >= 1 && H < 2) { + r = X; + g = C; + } else if (H >= 2 && H < 3) { + g = C; + b = X; + } else if (H >= 3 && H < 4) { + g = X; + b = C; + } else if (H >= 4 && H < 5) { + r = X; + b = C; + } else if (H >= 5 && H < 6) { + r = C; + b = X; + } + + r += m; + g += m; + b += m; + + r = parseInt(r * 255); + g = parseInt(g * 255); + b = parseInt(b * 255); + + return [r, g, b]; + }; + + /** + * Sets the color from a raw RGB888 integer + * @param raw RGB888 representation of color + */ + //todo: refactor into a static method + //todo: factor out individual color spaces + //todo: add HSL, CIELAB, and CIELUV + Color.prototype.set = function (val) { + this.raw = val; + + var r = (this.raw & 0xFF0000) >> 16; + var g = (this.raw & 0x00FF00) >> 8; + var b = (this.raw & 0x0000FF); + + // BT.709 + var y = 0.2126 * r + 0.7152 * g + 0.0722 * b; + var u = -0.09991 * r - 0.33609 * g + 0.436 * b; + var v = 0.615 * r - 0.55861 * g - 0.05639 * b; + + this.rgb = { + r: r, + g: g, + b: b + }; + + this.yuv = { + y: y, + u: u, + v: v + }; + + return this; + }; + + /** + * Lighten or darken a color + * @param multiplier Amount to lighten or darken (-1 to 1) + */ + Color.prototype.lighten = function(multiplier) { + var cm = Math.min(1, Math.max(0, Math.abs(multiplier))) * (multiplier < 0 ? -1 : 1); + var bm = (255 * cm) | 0; + var cr = Math.min(255, Math.max(0, this.rgb.r + bm)); + var cg = Math.min(255, Math.max(0, this.rgb.g + bm)); + var cb = Math.min(255, Math.max(0, this.rgb.b + bm)); + var hex = Color.rgb2hex(cr, cg, cb); + return new Color(hex); + }; + + /** + * Output color in hex format + * @param addHash Add a hash character to the beginning of the output + */ + Color.prototype.toHex = function(addHash) { + return (addHash ? '#' : '') + this.raw.toString(16); + }; + + /** + * Returns whether or not current color is lighter than another color + * @param color Color to compare against + */ + Color.prototype.lighterThan = function(color) { + if (!(color instanceof Color)) { + color = new Color(color); + } + + return this.yuv.y > color.yuv.y; + }; + + /** + * Returns the result of mixing current color with another color + * @param color Color to mix with + * @param multiplier How much to mix with the other color + */ + /* + Color.prototype.mix = function (color, multiplier) { + if (!(color instanceof Color)) { + color = new Color(color); + } + + var r = this.rgb.r; + var g = this.rgb.g; + var b = this.rgb.b; + var a = this.alpha; + + var m = typeof multiplier !== 'undefined' ? multiplier : 0.5; + + //todo: write a lerp function + r = r + m * (color.rgb.r - r); + g = g + m * (color.rgb.g - g); + b = b + m * (color.rgb.b - b); + a = a + m * (color.alpha - a); + + return new Color(Color.rgbToHex(r, g, b), { + 'alpha': a + }); + }; + */ + + /** + * Returns the result of blending another color on top of current color with alpha + * @param color Color to blend on top of current color, i.e. "Ca" + */ + //todo: see if .blendAlpha can be merged into .mix + Color.prototype.blendAlpha = function(color) { + if (!(color instanceof Color)) { + color = new Color(color); + } + + var Ca = color; + var Cb = this; + + //todo: write alpha blending function + var r = Ca.alpha * Ca.rgb.r + (1 - Ca.alpha) * Cb.rgb.r; + var g = Ca.alpha * Ca.rgb.g + (1 - Ca.alpha) * Cb.rgb.g; + var b = Ca.alpha * Ca.rgb.b + (1 - Ca.alpha) * Cb.rgb.b; + + return new Color(Color.rgb2hex(r, g, b)); + }; + + module.exports = Color; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports) { + + module.exports = { + 'version': '2.9.6', + 'svg_ns': 'http://www.w3.org/2000/svg' + }; + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + + var shaven = __webpack_require__(13); + + var SVG = __webpack_require__(8); + var constants = __webpack_require__(11); + var utils = __webpack_require__(7); + + var SVG_NS = constants.svg_ns; + + var templates = { + 'element': function (options) { + var tag = options.tag; + var content = options.content || ''; + delete options.tag; + delete options.content; + return [tag, content, options]; + } + }; + + //todo: deprecate tag arg, infer tag from shape object + function convertShape (shape, tag) { + return templates.element({ + 'tag': tag, + 'width': shape.width, + 'height': shape.height, + 'fill': shape.properties.fill + }); + } + + function textCss (properties) { + return utils.cssProps({ + 'fill': properties.fill, + 'font-weight': properties.font.weight, + 'font-family': properties.font.family + ', monospace', + 'font-size': properties.font.size + properties.font.units + }); + } + + function outlinePath (bgWidth, bgHeight, outlineWidth) { + var outlineOffsetWidth = outlineWidth / 2; + + return [ + 'M', outlineOffsetWidth, outlineOffsetWidth, + 'H', bgWidth - outlineOffsetWidth, + 'V', bgHeight - outlineOffsetWidth, + 'H', outlineOffsetWidth, + 'V', 0, + 'M', 0, outlineOffsetWidth, + 'L', bgWidth, bgHeight - outlineOffsetWidth, + 'M', 0, bgHeight - outlineOffsetWidth, + 'L', bgWidth, outlineOffsetWidth + ].join(' '); + } + + module.exports = function (sceneGraph, renderSettings) { + var engineSettings = renderSettings.engineSettings; + var stylesheets = engineSettings.stylesheets; + var stylesheetXml = stylesheets.map(function (stylesheet) { + return '<?xml-stylesheet rel="stylesheet" href="' + stylesheet + '"?>'; + }).join('\n'); + + var holderId = 'holder_' + Number(new Date()).toString(16); + + var root = sceneGraph.root; + var textGroup = root.children.holderTextGroup; + + var css = '#' + holderId + ' text { ' + textCss(textGroup.properties) + ' } '; + + // push text down to be equally vertically aligned with canvas renderer + textGroup.y += textGroup.textPositionData.boundingBox.height * 0.8; + + var wordTags = []; + + Object.keys(textGroup.children).forEach(function (lineKey) { + var line = textGroup.children[lineKey]; + + Object.keys(line.children).forEach(function (wordKey) { + var word = line.children[wordKey]; + var x = textGroup.x + line.x + word.x; + var y = textGroup.y + line.y + word.y; + var wordTag = templates.element({ + 'tag': 'text', + 'content': word.properties.text, + 'x': x, + 'y': y + }); + + wordTags.push(wordTag); + }); + }); + + var text = templates.element({ + 'tag': 'g', + 'content': wordTags + }); + + var outline = null; + + if (root.children.holderBg.properties.outline) { + var outlineProperties = root.children.holderBg.properties.outline; + outline = templates.element({ + 'tag': 'path', + 'd': outlinePath(root.children.holderBg.width, root.children.holderBg.height, outlineProperties.width), + 'stroke-width': outlineProperties.width, + 'stroke': outlineProperties.fill, + 'fill': 'none' + }); + } + + var bg = convertShape(root.children.holderBg, 'rect'); + + var sceneContent = []; + + sceneContent.push(bg); + if (outlineProperties) { + sceneContent.push(outline); + } + sceneContent.push(text); + + var scene = templates.element({ + 'tag': 'g', + 'id': holderId, + 'content': sceneContent + }); + + var style = templates.element({ + 'tag': 'style', + //todo: figure out how to add CDATA directive + 'content': css, + 'type': 'text/css' + }); + + var defs = templates.element({ + 'tag': 'defs', + 'content': style + }); + + var svg = templates.element({ + 'tag': 'svg', + 'content': [defs, scene], + 'width': root.properties.width, + 'height': root.properties.height, + 'xmlns': SVG_NS, + 'viewBox': [0, 0, root.properties.width, root.properties.height].join(' '), + 'preserveAspectRatio': 'none' + }); + + var output = shaven(svg); + + if (/\&(x)?#[0-9A-Fa-f]/.test(output[0])) { + output[0] = output[0].replace(/&#/gm, '&#'); + } + + output = stylesheetXml + output[0]; + + var svgString = SVG.svgStringToDataURI(output, renderSettings.mode === 'background'); + return svgString; + }; + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + + var escape = __webpack_require__(14) + + // TODO: remove namespace + + module.exports = function shaven (array, namespace, returnObject) { + + 'use strict' + + var i = 1 + var doesEscape = true + var HTMLString + var attributeKey + var callback + var key + + + returnObject = returnObject || {} + + + function createElement (sugarString) { + + var tags = sugarString.match(/^[\w-]+/) + var element = { + tag: tags ? tags[0] : 'div', + attr: {}, + children: [] + } + var id = sugarString.match(/#([\w-]+)/) + var reference = sugarString.match(/\$([\w-]+)/) + var classNames = sugarString.match(/\.[\w-]+/g) + + + // Assign id if is set + if (id) { + element.attr.id = id[1] + + // Add element to the return object + returnObject[id[1]] = element + } + + if (reference) + returnObject[reference[1]] = element + + if (classNames) + element.attr.class = classNames.join(' ').replace(/\./g, '') + + if (sugarString.match(/&$/g)) + doesEscape = false + + return element + } + + function replacer (key, value) { + + if (value === null || value === false || value === undefined) + return + + if (typeof value !== 'string' && typeof value !== 'object') + return String(value) + + return value + } + + function escapeAttribute (string) { + return (string || string === 0) ? + String(string) + .replace(/&/g, '&') + .replace(/"/g, '"') : + '' + } + + function escapeHTML (string) { + return String(string) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/</g, '<') + .replace(/>/g, '>') + } + + + if (typeof array[0] === 'string') + array[0] = createElement(array[0]) + + else if (Array.isArray(array[0])) + i = 0 + + else + throw new Error( + 'First element of array must be a string, ' + + 'or an array and not ' + JSON.stringify(array[0]) + ) + + + for (; i < array.length; i++) { + + // Don't render element if value is false or null + if (array[i] === false || array[i] === null) { + array[0] = false + break + } + + // Continue with next array value if current value is undefined or true + else if (array[i] === undefined || array[i] === true) { + continue + } + + else if (typeof array[i] === 'string') { + if (doesEscape) + array[i] = escapeHTML(array[i]) + + array[0].children.push(array[i]) + } + + else if (typeof array[i] === 'number') { + + array[0].children.push(array[i]) + } + + else if (Array.isArray(array[i])) { + + if (Array.isArray(array[i][0])) { + array[i].reverse().forEach(function (subArray) { + array.splice(i + 1, 0, subArray) + }) + + if (i !== 0) + continue + i++ + } + + shaven(array[i], namespace, returnObject) + + if (array[i][0]) + array[0].children.push(array[i][0]) + } + + else if (typeof array[i] === 'function') + callback = array[i] + + + else if (typeof array[i] === 'object') { + for (attributeKey in array[i]) + if (array[i].hasOwnProperty(attributeKey)) + if (array[i][attributeKey] !== null && + array[i][attributeKey] !== false) + if (attributeKey === 'style' && + typeof array[i][attributeKey] === 'object') + array[0].attr[attributeKey] = JSON + .stringify(array[i][attributeKey], replacer) + .slice(2, -2) + .replace(/","/g, ';') + .replace(/":"/g, ':') + .replace(/\\"/g, '\'') + + else + array[0].attr[attributeKey] = array[i][attributeKey] + } + + else + throw new TypeError('"' + array[i] + '" is not allowed as a value.') + } + + + if (array[0] !== false) { + + HTMLString = '<' + array[0].tag + + for (key in array[0].attr) + if (array[0].attr.hasOwnProperty(key)) + HTMLString += ' ' + key + '="' + + escapeAttribute(array[0].attr[key]) + '"' + + HTMLString += '>' + + array[0].children.forEach(function (child) { + HTMLString += child + }) + + HTMLString += '</' + array[0].tag + '>' + + array[0] = HTMLString + } + + // Return root element on index 0 + returnObject[0] = array[0] + + if (callback) + callback(array[0]) + + // returns object containing all elements with an id and the root element + return returnObject + } + + +/***/ }), +/* 14 */ +/***/ (function(module, exports) { + + /*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */ + + 'use strict'; + + /** + * Module variables. + * @private + */ + + var matchHtmlRegExp = /["'&<>]/; + + /** + * Module exports. + * @public + */ + + module.exports = escapeHtml; + + /** + * Escape special characters in the given string of html. + * + * @param {string} string The string to escape for inserting into HTML + * @return {string} + * @public + */ + + function escapeHtml(string) { + var str = '' + string; + var match = matchHtmlRegExp.exec(str); + + if (!match) { + return str; + } + + var escape; + var html = ''; + var index = 0; + var lastIndex = 0; + + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"'; + break; + case 38: // & + escape = '&'; + break; + case 39: // ' + escape = '''; + break; + case 60: // < + escape = '<'; + break; + case 62: // > + escape = '>'; + break; + default: + continue; + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index); + } + + lastIndex = index + 1; + html += escape; + } + + return lastIndex !== index + ? html + str.substring(lastIndex, index) + : html; + } + + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + + var DOM = __webpack_require__(9); + var utils = __webpack_require__(7); + + module.exports = (function() { + var canvas = DOM.newEl('canvas'); + var ctx = null; + + return function(sceneGraph) { + if (ctx == null) { + ctx = canvas.getContext('2d'); + } + + var dpr = utils.canvasRatio(); + var root = sceneGraph.root; + canvas.width = dpr * root.properties.width; + canvas.height = dpr * root.properties.height ; + ctx.textBaseline = 'middle'; + + var bg = root.children.holderBg; + var bgWidth = dpr * bg.width; + var bgHeight = dpr * bg.height; + //todo: parametrize outline width (e.g. in scene object) + var outlineWidth = 2; + var outlineOffsetWidth = outlineWidth / 2; + + ctx.fillStyle = bg.properties.fill; + ctx.fillRect(0, 0, bgWidth, bgHeight); + + if (bg.properties.outline) { + //todo: abstract this into a method + ctx.strokeStyle = bg.properties.outline.fill; + ctx.lineWidth = bg.properties.outline.width; + ctx.moveTo(outlineOffsetWidth, outlineOffsetWidth); + // TL, TR, BR, BL + ctx.lineTo(bgWidth - outlineOffsetWidth, outlineOffsetWidth); + ctx.lineTo(bgWidth - outlineOffsetWidth, bgHeight - outlineOffsetWidth); + ctx.lineTo(outlineOffsetWidth, bgHeight - outlineOffsetWidth); + ctx.lineTo(outlineOffsetWidth, outlineOffsetWidth); + // Diagonals + ctx.moveTo(0, outlineOffsetWidth); + ctx.lineTo(bgWidth, bgHeight - outlineOffsetWidth); + ctx.moveTo(0, bgHeight - outlineOffsetWidth); + ctx.lineTo(bgWidth, outlineOffsetWidth); + ctx.stroke(); + } + + var textGroup = root.children.holderTextGroup; + ctx.font = textGroup.properties.font.weight + ' ' + (dpr * textGroup.properties.font.size) + textGroup.properties.font.units + ' ' + textGroup.properties.font.family + ', monospace'; + ctx.fillStyle = textGroup.properties.fill; + + for (var lineKey in textGroup.children) { + var line = textGroup.children[lineKey]; + for (var wordKey in line.children) { + var word = line.children[wordKey]; + var x = dpr * (textGroup.x + line.x + word.x); + var y = dpr * (textGroup.y + line.y + word.y + (textGroup.properties.leading / 2)); + + ctx.fillText(word.properties.text, x, y); + } + } + + return canvas.toDataURL('image/png'); + }; + })(); + +/***/ }) +/******/ ]) +}); +; +(function(ctx, isMeteorPackage) { + if (isMeteorPackage) { + Holder = ctx.Holder; + } +})(this, typeof Meteor !== 'undefined' && typeof Package !== 'undefined'); |
