hjg
2023-11-18 bb48edb3d9faaaeab0088151c86fc24137acdb08
提交 | 用户 | 时间
58d006 1 /*!
A 2
3 Holder - client side image placeholders
4 Version 2.8.0+7srgw
5 © 2015 Ivan Malopinsky - http://imsky.co
6
7 Site:     http://holderjs.com
8 Issues:   https://github.com/imsky/holder/issues
9 License:  MIT
10
11 */
12 (function (window) {
13   if (!window.document) return;
14   var document = window.document;
15
16   //https://github.com/inexorabletash/polyfill/blob/master/web.js
17     if (!document.querySelectorAll) {
18       document.querySelectorAll = function (selectors) {
19         var style = document.createElement('style'), elements = [], element;
20         document.documentElement.firstChild.appendChild(style);
21         document._qsa = [];
22
23         style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
24         window.scrollBy(0, 0);
25         style.parentNode.removeChild(style);
26
27         while (document._qsa.length) {
28           element = document._qsa.shift();
29           element.style.removeAttribute('x-qsa');
30           elements.push(element);
31         }
32         document._qsa = null;
33         return elements;
34       };
35     }
36
37     if (!document.querySelector) {
38       document.querySelector = function (selectors) {
39         var elements = document.querySelectorAll(selectors);
40         return (elements.length) ? elements[0] : null;
41       };
42     }
43
44     if (!document.getElementsByClassName) {
45       document.getElementsByClassName = function (classNames) {
46         classNames = String(classNames).replace(/^|\s+/g, '.');
47         return document.querySelectorAll(classNames);
48       };
49     }
50
51   //https://github.com/inexorabletash/polyfill
52   // ES5 15.2.3.14 Object.keys ( O )
53   // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
54   if (!Object.keys) {
55     Object.keys = function (o) {
56       if (o !== Object(o)) { throw TypeError('Object.keys called on non-object'); }
57       var ret = [], p;
58       for (p in o) {
59         if (Object.prototype.hasOwnProperty.call(o, p)) {
60           ret.push(p);
61         }
62       }
63       return ret;
64     };
65   }
66
67   //https://github.com/inexorabletash/polyfill/blob/master/web.js
68   (function (global) {
69     var B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
70     global.atob = global.atob || function (input) {
71       input = String(input);
72       var position = 0,
73           output = [],
74           buffer = 0, bits = 0, n;
75
76       input = input.replace(/\s/g, '');
77       if ((input.length % 4) === 0) { input = input.replace(/=+$/, ''); }
78       if ((input.length % 4) === 1) { throw Error('InvalidCharacterError'); }
79       if (/[^+/0-9A-Za-z]/.test(input)) { throw Error('InvalidCharacterError'); }
80
81       while (position < input.length) {
82         n = B64_ALPHABET.indexOf(input.charAt(position));
83         buffer = (buffer << 6) | n;
84         bits += 6;
85
86         if (bits === 24) {
87           output.push(String.fromCharCode((buffer >> 16) & 0xFF));
88           output.push(String.fromCharCode((buffer >>  8) & 0xFF));
89           output.push(String.fromCharCode(buffer & 0xFF));
90           bits = 0;
91           buffer = 0;
92         }
93         position += 1;
94       }
95
96       if (bits === 12) {
97         buffer = buffer >> 4;
98         output.push(String.fromCharCode(buffer & 0xFF));
99       } else if (bits === 18) {
100         buffer = buffer >> 2;
101         output.push(String.fromCharCode((buffer >> 8) & 0xFF));
102         output.push(String.fromCharCode(buffer & 0xFF));
103       }
104
105       return output.join('');
106     };
107
108     global.btoa = global.btoa || function (input) {
109       input = String(input);
110       var position = 0,
111           out = [],
112           o1, o2, o3,
113           e1, e2, e3, e4;
114
115       if (/[^\x00-\xFF]/.test(input)) { throw Error('InvalidCharacterError'); }
116
117       while (position < input.length) {
118         o1 = input.charCodeAt(position++);
119         o2 = input.charCodeAt(position++);
120         o3 = input.charCodeAt(position++);
121
122         // 111111 112222 222233 333333
123         e1 = o1 >> 2;
124         e2 = ((o1 & 0x3) << 4) | (o2 >> 4);
125         e3 = ((o2 & 0xf) << 2) | (o3 >> 6);
126         e4 = o3 & 0x3f;
127
128         if (position === input.length + 2) {
129           e3 = 64; e4 = 64;
130         }
131         else if (position === input.length + 1) {
132           e4 = 64;
133         }
134
135         out.push(B64_ALPHABET.charAt(e1),
136                  B64_ALPHABET.charAt(e2),
137                  B64_ALPHABET.charAt(e3),
138                  B64_ALPHABET.charAt(e4));
139       }
140
141       return out.join('');
142     };
143   }(window));
144
145   //https://gist.github.com/jimeh/332357
146   if (!Object.prototype.hasOwnProperty){
147       /*jshint -W001, -W103 */
148       Object.prototype.hasOwnProperty = function(prop) {
149       var proto = this.__proto__ || this.constructor.prototype;
150       return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]);
151     };
152       /*jshint +W001, +W103 */
153   }
154
155   // @license http://opensource.org/licenses/MIT
156   // copyright Paul Irish 2015
157
158
159   // Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill
160   //   github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js
161   // as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values
162
163   // if you want values similar to what you'd get with real perf.now, place this towards the head of the page
164   // but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed
165
166
167   (function(){
168
169     if ('performance' in window === false) {
170         window.performance = {};
171     }
172     
173     Date.now = (Date.now || function () {  // thanks IE8
174       return new Date().getTime();
175     });
176
177     if ('now' in window.performance === false){
178       
179       var nowOffset = Date.now();
180       
181       if (performance.timing && performance.timing.navigationStart){
182         nowOffset = performance.timing.navigationStart;
183       }
184
185       window.performance.now = function now(){
186         return Date.now() - nowOffset;
187       };
188     }
189
190   })();
191
192   //requestAnimationFrame polyfill for older Firefox/Chrome versions
193   if (!window.requestAnimationFrame) {
194     if (window.webkitRequestAnimationFrame) {
195     //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-webkit.js
196     (function (global) {
197       // window.requestAnimationFrame
198       global.requestAnimationFrame = function (callback) {
199         return webkitRequestAnimationFrame(function () {
200           callback(global.performance.now());
201         });
202       };
203
204       // window.cancelAnimationFrame
205       global.cancelAnimationFrame = webkitCancelAnimationFrame;
206     }(window));
207     } else if (window.mozRequestAnimationFrame) {
208       //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-moz.js
209     (function (global) {
210       // window.requestAnimationFrame
211       global.requestAnimationFrame = function (callback) {
212         return mozRequestAnimationFrame(function () {
213           callback(global.performance.now());
214         });
215       };
216
217       // window.cancelAnimationFrame
218       global.cancelAnimationFrame = mozCancelAnimationFrame;
219     }(window));
220     } else {
221     (function (global) {
222       global.requestAnimationFrame = function (callback) {
223       return global.setTimeout(callback, 1000 / 60);
224       };
225
226       global.cancelAnimationFrame = global.clearTimeout;
227     })(window);
228     }
229   }
230 })(this);
231
232 (function webpackUniversalModuleDefinition(root, factory) {
233     if(typeof exports === 'object' && typeof module === 'object')
234         module.exports = factory();
235     else if(typeof define === 'function' && define.amd)
236         define(factory);
237     else if(typeof exports === 'object')
238         exports["Holder"] = factory();
239     else
240         root["Holder"] = factory();
241 })(this, function() {
242 return /******/ (function(modules) { // webpackBootstrap
243 /******/     // The module cache
244 /******/     var installedModules = {};
245
246 /******/     // The require function
247 /******/     function __webpack_require__(moduleId) {
248
249 /******/         // Check if module is in cache
250 /******/         if(installedModules[moduleId])
251 /******/             return installedModules[moduleId].exports;
252
253 /******/         // Create a new module (and put it into the cache)
254 /******/         var module = installedModules[moduleId] = {
255 /******/             exports: {},
256 /******/             id: moduleId,
257 /******/             loaded: false
258 /******/         };
259
260 /******/         // Execute the module function
261 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
262
263 /******/         // Flag the module as loaded
264 /******/         module.loaded = true;
265
266 /******/         // Return the exports of the module
267 /******/         return module.exports;
268 /******/     }
269
270
271 /******/     // expose the modules object (__webpack_modules__)
272 /******/     __webpack_require__.m = modules;
273
274 /******/     // expose the module cache
275 /******/     __webpack_require__.c = installedModules;
276
277 /******/     // __webpack_public_path__
278 /******/     __webpack_require__.p = "";
279
280 /******/     // Load entry module and return exports
281 /******/     return __webpack_require__(0);
282 /******/ })
283 /************************************************************************/
284 /******/ ([
285 /* 0 */
286 /***/ function(module, exports, __webpack_require__) {
287
288     /*
289     Holder.js - client side image placeholders
290     (c) 2012-2015 Ivan Malopinsky - http://imsky.co
291     */
292
293     module.exports = __webpack_require__(1);
294
295
296 /***/ },
297 /* 1 */
298 /***/ function(module, exports, __webpack_require__) {
299
300     /* WEBPACK VAR INJECTION */(function(global) {/*
301     Holder.js - client side image placeholders
302     (c) 2012-2015 Ivan Malopinsky - http://imsky.co
303     */
304
305     //Libraries and functions
306     var onDomReady = __webpack_require__(3);
307     var querystring = __webpack_require__(2);
308
309     var SceneGraph = __webpack_require__(4);
310     var utils = __webpack_require__(5);
311     var SVG = __webpack_require__(6);
312     var DOM = __webpack_require__(7);
313     var Color = __webpack_require__(8);
314
315     var extend = utils.extend;
316     var dimensionCheck = utils.dimensionCheck;
317
318     //Constants and definitions
319     var SVG_NS = 'http://www.w3.org/2000/svg';
320     var NODE_TYPE_COMMENT = 8;
321     var version = '2.8.0';
322     var generatorComment = '\n' +
323         'Created with Holder.js ' + version + '.\n' +
324         'Learn more at http://holderjs.com\n' +
325         '(c) 2012-2015 Ivan Malopinsky - http://imsky.co\n';
326
327     var Holder = {
328         version: version,
329
330         /**
331          * Adds a theme to default settings
332          *
333          * @param {string} name Theme name
334          * @param {Object} theme Theme object, with foreground, background, size, font, and fontweight properties.
335          */
336         addTheme: function(name, theme) {
337             name != null && theme != null && (App.settings.themes[name] = theme);
338             delete App.vars.cache.themeKeys;
339             return this;
340         },
341
342         /**
343          * Appends a placeholder to an element
344          *
345          * @param {string} src Placeholder URL string
346          * @param el A selector or a reference to a DOM node
347          */
348         addImage: function(src, el) {
349             //todo: use jquery fallback if available for all QSA references
350             var node = DOM.getNodeArray(el);
351             if (node.length) {
352                 for (var i = 0, l = node.length; i < l; i++) {
353                     var img = DOM.newEl('img');
354                     var domProps = {};
355                     domProps[App.setup.dataAttr] = src;
356                     DOM.setAttr(img, domProps);
357                     node[i].appendChild(img);
358                 }
359             }
360             return this;
361         },
362
363         /**
364          * Sets whether or not an image is updated on resize.
365          * If an image is set to be updated, it is immediately rendered.
366          *
367          * @param {Object} el Image DOM element
368          * @param {Boolean} value Resizable update flag value
369          */
370         setResizeUpdate: function(el, value) {
371             if (el.holderData) {
372                 el.holderData.resizeUpdate = !!value;
373                 if (el.holderData.resizeUpdate) {
374                     updateResizableElements(el);
375                 }
376             }
377         },
378
379         /**
380          * Runs Holder with options. By default runs Holder on all images with "holder.js" in their source attributes.
381          *
382          * @param {Object} userOptions Options object, can contain domain, themes, images, and bgnodes properties
383          */
384         run: function(userOptions) {
385             //todo: split processing into separate queues
386             userOptions = userOptions || {};
387             var engineSettings = {};
388             var options = extend(App.settings, userOptions);
389
390             App.vars.preempted = true;
391             App.vars.dataAttr = options.dataAttr || App.setup.dataAttr;
392             App.vars.lineWrapRatio = options.lineWrapRatio || App.setup.lineWrapRatio;
393
394             engineSettings.renderer = options.renderer ? options.renderer : App.setup.renderer;
395             if (App.setup.renderers.join(',').indexOf(engineSettings.renderer) === -1) {
396                 engineSettings.renderer = App.setup.supportsSVG ? 'svg' : (App.setup.supportsCanvas ? 'canvas' : 'html');
397             }
398
399             var images = DOM.getNodeArray(options.images);
400             var bgnodes = DOM.getNodeArray(options.bgnodes);
401             var stylenodes = DOM.getNodeArray(options.stylenodes);
402             var objects = DOM.getNodeArray(options.objects);
403
404             engineSettings.stylesheets = [];
405             engineSettings.svgXMLStylesheet = true;
406             engineSettings.noFontFallback = options.noFontFallback ? options.noFontFallback : false;
407
408             for (var i = 0; i < stylenodes.length; i++) {
409                 var styleNode = stylenodes[i];
410                 if (styleNode.attributes.rel && styleNode.attributes.href && styleNode.attributes.rel.value == 'stylesheet') {
411                     var href = styleNode.attributes.href.value;
412                     //todo: write isomorphic relative-to-absolute URL function
413                     var proxyLink = DOM.newEl('a');
414                     proxyLink.href = href;
415                     var stylesheetURL = proxyLink.protocol + '//' + proxyLink.host + proxyLink.pathname + proxyLink.search;
416                     engineSettings.stylesheets.push(stylesheetURL);
417                 }
418             }
419
420             for (i = 0; i < bgnodes.length; i++) {
421                 //Skip processing background nodes if getComputedStyle is unavailable, since only modern browsers would be able to use canvas or SVG to render to background
422                 if (!global.getComputedStyle) continue;
423                 var backgroundImage = global.getComputedStyle(bgnodes[i], null).getPropertyValue('background-image');
424                 var dataBackgroundImage = bgnodes[i].getAttribute('data-background-src');
425                 var rawURL = dataBackgroundImage || backgroundImage;
426
427                 var holderURL = null;
428                 var holderString = '?' + options.domain + '/';
429
430                 if (rawURL.indexOf(holderString) === 0) {
431                     holderURL = rawURL.slice(1);
432                 } else if (rawURL.indexOf(holderString) != -1) {
433                     var fragment = rawURL.substr(rawURL.indexOf(holderString)).slice(1);
434                     var fragmentMatch = fragment.match(/([^\"]*)"?\)/);
435
436                     if (fragmentMatch != null) {
437                         holderURL = fragmentMatch[1];
438                     }
439                 }
440
441                 if (holderURL != null) {
442                     var holderFlags = parseURL(holderURL, options);
443                     if (holderFlags) {
444                         prepareDOMElement({
445                             mode: 'background',
446                             el: bgnodes[i],
447                             flags: holderFlags,
448                             engineSettings: engineSettings
449                         });
450                     }
451                 }
452             }
453
454             for (i = 0; i < objects.length; i++) {
455                 var object = objects[i];
456                 var objectAttr = {};
457
458                 try {
459                     objectAttr.data = object.getAttribute('data');
460                     objectAttr.dataSrc = object.getAttribute(App.vars.dataAttr);
461                 } catch (e) {}
462
463                 var objectHasSrcURL = objectAttr.data != null && objectAttr.data.indexOf(options.domain) === 0;
464                 var objectHasDataSrcURL = objectAttr.dataSrc != null && objectAttr.dataSrc.indexOf(options.domain) === 0;
465
466                 if (objectHasSrcURL) {
467                     prepareImageElement(options, engineSettings, objectAttr.data, object);
468                 } else if (objectHasDataSrcURL) {
469                     prepareImageElement(options, engineSettings, objectAttr.dataSrc, object);
470                 }
471             }
472
473             for (i = 0; i < images.length; i++) {
474                 var image = images[i];
475                 var imageAttr = {};
476
477                 try {
478                     imageAttr.src = image.getAttribute('src');
479                     imageAttr.dataSrc = image.getAttribute(App.vars.dataAttr);
480                     imageAttr.rendered = image.getAttribute('data-holder-rendered');
481                 } catch (e) {}
482
483                 var imageHasSrc = imageAttr.src != null;
484                 var imageHasDataSrcURL = imageAttr.dataSrc != null && imageAttr.dataSrc.indexOf(options.domain) === 0;
485                 var imageRendered = imageAttr.rendered != null && imageAttr.rendered == 'true';
486
487                 if (imageHasSrc) {
488                     if (imageAttr.src.indexOf(options.domain) === 0) {
489                         prepareImageElement(options, engineSettings, imageAttr.src, image);
490                     } else if (imageHasDataSrcURL) {
491                         //Image has a valid data-src and an invalid src
492                         if (imageRendered) {
493                             //If the placeholder has already been render, re-render it
494                             prepareImageElement(options, engineSettings, imageAttr.dataSrc, image);
495                         } else {
496                             //If the placeholder has not been rendered, check if the image exists and render a fallback if it doesn't
497                             (function(src, options, engineSettings, dataSrc, image) {
498                                 utils.imageExists(src, function(exists) {
499                                     if (!exists) {
500                                         prepareImageElement(options, engineSettings, dataSrc, image);
501                                     }
502                                 });
503                             })(imageAttr.src, options, engineSettings, imageAttr.dataSrc, image);
504                         }
505                     }
506                 } else if (imageHasDataSrcURL) {
507                     prepareImageElement(options, engineSettings, imageAttr.dataSrc, image);
508                 }
509             }
510
511             return this;
512         }
513     };
514
515     var App = {
516         settings: {
517             domain: 'holder.js',
518             images: 'img',
519             objects: 'object',
520             bgnodes: 'body .holderjs',
521             stylenodes: 'head link.holderjs',
522             themes: {
523                 'gray': {
524                     background: '#EEEEEE',
525                     foreground: '#AAAAAA'
526                 },
527                 'social': {
528                     background: '#3a5a97',
529                     foreground: '#FFFFFF'
530                 },
531                 'industrial': {
532                     background: '#434A52',
533                     foreground: '#C2F200'
534                 },
535                 'sky': {
536                     background: '#0D8FDB',
537                     foreground: '#FFFFFF'
538                 },
539                 'vine': {
540                     background: '#39DBAC',
541                     foreground: '#1E292C'
542                 },
543                 'lava': {
544                     background: '#F8591A',
545                     foreground: '#1C2846'
546                 }
547             }
548         },
549         defaults: {
550             size: 10,
551             units: 'pt',
552             scale: 1 / 16
553         }
554     };
555
556     /**
557      * Processes provided source attribute and sets up the appropriate rendering workflow
558      *
559      * @private
560      * @param options Instance options from Holder.run
561      * @param renderSettings Instance configuration
562      * @param src Image URL
563      * @param el Image DOM element
564      */
565     function prepareImageElement(options, engineSettings, src, el) {
566         var holderFlags = parseURL(src.substr(src.lastIndexOf(options.domain)), options);
567         if (holderFlags) {
568             prepareDOMElement({
569                 mode: null,
570                 el: el,
571                 flags: holderFlags,
572                 engineSettings: engineSettings
573             });
574         }
575     }
576
577     /**
578      * Processes a Holder URL
579      *
580      * @private
581      * @param url URL
582      * @param options Instance options from Holder.run
583      */
584     function parseURL(url, options) {
585         var holder = {
586             theme: extend(App.settings.themes.gray, null),
587             stylesheets: options.stylesheets,
588             instanceOptions: options
589         };
590
591         return parseQueryString(url, holder);
592     }
593
594     /**
595      * Processes a Holder URL and extracts configuration from query string
596      *
597      * @private
598      * @param url URL
599      * @param holder Staging Holder object
600      */
601     function parseQueryString(url, holder) {
602         var parts = url.split('?');
603         var basics = parts[0].split('/');
604
605         holder.holderURL = url;
606
607         var dimensions = basics[1];
608         var dimensionData = dimensions.match(/([\d]+p?)x([\d]+p?)/);
609
610         if (!dimensionData) return false;
611
612         holder.fluid = dimensions.indexOf('p') !== -1;
613
614         holder.dimensions = {
615             width: dimensionData[1].replace('p', '%'),
616             height: dimensionData[2].replace('p', '%')
617         };
618
619         if (parts.length === 2) {
620             var options = querystring.parse(parts[1]);
621
622             // Colors
623
624             if (options.bg) {
625                 holder.theme.background = (options.bg.indexOf('#') === -1 ? '#' : '') + options.bg;
626             }
627
628             if (options.fg) {
629                 holder.theme.foreground = (options.fg.indexOf('#') === -1 ? '#' : '') + options.fg;
630             }
631
632             //todo: add automatic foreground to themes without foreground
633             if (options.bg && !options.fg) {
634                 holder.autoFg = true;
635             }
636
637             if (options.theme && holder.instanceOptions.themes.hasOwnProperty(options.theme)) {
638                 holder.theme = extend(holder.instanceOptions.themes[options.theme], null);
639             }
640
641             // Text
642
643             if (options.text) {
644                 holder.text = options.text;
645             }
646
647             if (options.textmode) {
648                 holder.textmode = options.textmode;
649             }
650
651             if (options.size) {
652                 holder.size = options.size;
653             }
654
655             if (options.font) {
656                 holder.font = options.font;
657             }
658
659             if (options.align) {
660                 holder.align = options.align;
661             }
662
663             holder.nowrap = utils.truthy(options.nowrap);
664
665             // Miscellaneous
666
667             holder.auto = utils.truthy(options.auto);
668
669             holder.outline = utils.truthy(options.outline);
670
671             if (utils.truthy(options.random)) {
672                 App.vars.cache.themeKeys = App.vars.cache.themeKeys || Object.keys(holder.instanceOptions.themes);
673                 var _theme = App.vars.cache.themeKeys[0 | Math.random() * App.vars.cache.themeKeys.length];
674                 holder.theme = extend(holder.instanceOptions.themes[_theme], null);
675             }
676         }
677
678         return holder;
679     }
680
681     /**
682      * Modifies the DOM to fit placeholders and sets up resizable image callbacks (for fluid and automatically sized placeholders)
683      *
684      * @private
685      * @param settings DOM prep settings
686      */
687     function prepareDOMElement(prepSettings) {
688         var mode = prepSettings.mode;
689         var el = prepSettings.el;
690         var flags = prepSettings.flags;
691         var _engineSettings = prepSettings.engineSettings;
692         var dimensions = flags.dimensions,
693             theme = flags.theme;
694         var dimensionsCaption = dimensions.width + 'x' + dimensions.height;
695         mode = mode == null ? (flags.fluid ? 'fluid' : 'image') : mode;
696
697         if (flags.text != null) {
698             theme.text = flags.text;
699
700             //<object> SVG embedding doesn't parse Unicode properly
701             if (el.nodeName.toLowerCase() === 'object') {
702                 var textLines = theme.text.split('\\n');
703                 for (var k = 0; k < textLines.length; k++) {
704                     textLines[k] = utils.encodeHtmlEntity(textLines[k]);
705                 }
706                 theme.text = textLines.join('\\n');
707             }
708         }
709
710         var holderURL = flags.holderURL;
711         var engineSettings = extend(_engineSettings, null);
712
713         if (flags.font) {
714             theme.font = flags.font;
715             //Only run the <canvas> webfont fallback if noFontFallback is false, if the node is not an image, and if canvas is supported
716             if (!engineSettings.noFontFallback && el.nodeName.toLowerCase() === 'img' && App.setup.supportsCanvas && engineSettings.renderer === 'svg') {
717                 engineSettings = extend(engineSettings, {
718                     renderer: 'canvas'
719                 });
720             }
721         }
722
723         //Chrome and Opera require a quick 10ms re-render if web fonts are used with canvas
724         if (flags.font && engineSettings.renderer == 'canvas') {
725             engineSettings.reRender = true;
726         }
727
728         if (mode == 'background') {
729             if (el.getAttribute('data-background-src') == null) {
730                 DOM.setAttr(el, {
731                     'data-background-src': holderURL
732                 });
733             }
734         } else {
735             var domProps = {};
736             domProps[App.vars.dataAttr] = holderURL;
737             DOM.setAttr(el, domProps);
738         }
739
740         flags.theme = theme;
741
742         //todo consider using all renderSettings in holderData
743         el.holderData = {
744             flags: flags,
745             engineSettings: engineSettings
746         };
747
748         if (mode == 'image' || mode == 'fluid') {
749             DOM.setAttr(el, {
750                 'alt': (theme.text ? theme.text + ' [' + dimensionsCaption + ']' : dimensionsCaption)
751             });
752         }
753
754         var renderSettings = {
755             mode: mode,
756             el: el,
757             holderSettings: {
758                 dimensions: dimensions,
759                 theme: theme,
760                 flags: flags
761             },
762             engineSettings: engineSettings
763         };
764
765         if (mode == 'image') {
766             if (engineSettings.renderer == 'html' || !flags.auto) {
767                 el.style.width = dimensions.width + 'px';
768                 el.style.height = dimensions.height + 'px';
769             }
770             if (engineSettings.renderer == 'html') {
771                 el.style.backgroundColor = theme.background;
772             } else {
773                 render(renderSettings);
774
775                 if (flags.textmode == 'exact') {
776                     el.holderData.resizeUpdate = true;
777                     App.vars.resizableImages.push(el);
778                     updateResizableElements(el);
779                 }
780             }
781         } else if (mode == 'background' && engineSettings.renderer != 'html') {
782             render(renderSettings);
783         } else if (mode == 'fluid') {
784             el.holderData.resizeUpdate = true;
785
786             if (dimensions.height.slice(-1) == '%') {
787                 el.style.height = dimensions.height;
788             } else if (flags.auto == null || !flags.auto) {
789                 el.style.height = dimensions.height + 'px';
790             }
791             if (dimensions.width.slice(-1) == '%') {
792                 el.style.width = dimensions.width;
793             } else if (flags.auto == null || !flags.auto) {
794                 el.style.width = dimensions.width + 'px';
795             }
796             if (el.style.display == 'inline' || el.style.display === '' || el.style.display == 'none') {
797                 el.style.display = 'block';
798             }
799
800             setInitialDimensions(el);
801
802             if (engineSettings.renderer == 'html') {
803                 el.style.backgroundColor = theme.background;
804             } else {
805                 App.vars.resizableImages.push(el);
806                 updateResizableElements(el);
807             }
808         }
809     }
810
811     /**
812      * Core function that takes output from renderers and sets it as the source or background-image of the target element
813      *
814      * @private
815      * @param renderSettings Renderer settings
816      */
817     function render(renderSettings) {
818         var image = null;
819         var mode = renderSettings.mode;
820         var el = renderSettings.el;
821         var holderSettings = renderSettings.holderSettings;
822         var engineSettings = renderSettings.engineSettings;
823
824         switch (engineSettings.renderer) {
825             case 'svg':
826                 if (!App.setup.supportsSVG) return;
827                 break;
828             case 'canvas':
829                 if (!App.setup.supportsCanvas) return;
830                 break;
831             default:
832                 return;
833         }
834
835         //todo: move generation of scene up to flag generation to reduce extra object creation
836         var scene = {
837             width: holderSettings.dimensions.width,
838             height: holderSettings.dimensions.height,
839             theme: holderSettings.theme,
840             flags: holderSettings.flags
841         };
842
843         var sceneGraph = buildSceneGraph(scene);
844
845         function getRenderedImage() {
846             var image = null;
847             switch (engineSettings.renderer) {
848                 case 'canvas':
849                     image = sgCanvasRenderer(sceneGraph, renderSettings);
850                     break;
851                 case 'svg':
852                     image = sgSVGRenderer(sceneGraph, renderSettings);
853                     break;
854                 default:
855                     throw 'Holder: invalid renderer: ' + engineSettings.renderer;
856             }
857
858             return image;
859         }
860
861         image = getRenderedImage();
862
863         if (image == null) {
864             throw 'Holder: couldn\'t render placeholder';
865         }
866
867         //todo: add <object> canvas rendering
868         if (mode == 'background') {
869             el.style.backgroundImage = 'url(' + image + ')';
870             el.style.backgroundSize = scene.width + 'px ' + scene.height + 'px';
871         } else {
872             if (el.nodeName.toLowerCase() === 'img') {
873                 DOM.setAttr(el, {
874                     'src': image
875                 });
876             } else if (el.nodeName.toLowerCase() === 'object') {
877                 DOM.setAttr(el, {
878                     'data': image
879                 });
880                 DOM.setAttr(el, {
881                     'type': 'image/svg+xml'
882                 });
883             }
884             if (engineSettings.reRender) {
885                 global.setTimeout(function() {
886                     var image = getRenderedImage();
887                     if (image == null) {
888                         throw 'Holder: couldn\'t render placeholder';
889                     }
890                     //todo: refactor this code into a function
891                     if (el.nodeName.toLowerCase() === 'img') {
892                         DOM.setAttr(el, {
893                             'src': image
894                         });
895                     } else if (el.nodeName.toLowerCase() === 'object') {
896                         DOM.setAttr(el, {
897                             'data': image
898                         });
899                         DOM.setAttr(el, {
900                             'type': 'image/svg+xml'
901                         });
902                     }
903                 }, 150);
904             }
905         }
906         //todo: account for re-rendering
907         DOM.setAttr(el, {
908             'data-holder-rendered': true
909         });
910     }
911
912     /**
913      * Core function that takes a Holder scene description and builds a scene graph
914      *
915      * @private
916      * @param scene Holder scene object
917      */
918     //todo: make this function reusable
919     //todo: merge app defaults and setup properties into the scene argument
920     function buildSceneGraph(scene) {
921         var fontSize = App.defaults.size;
922         if (parseFloat(scene.theme.size)) {
923             fontSize = scene.theme.size;
924         } else if (parseFloat(scene.flags.size)) {
925             fontSize = scene.flags.size;
926         }
927
928         scene.font = {
929             family: scene.theme.font ? scene.theme.font : 'Arial, Helvetica, Open Sans, sans-serif',
930             size: textSize(scene.width, scene.height, fontSize),
931             units: scene.theme.units ? scene.theme.units : App.defaults.units,
932             weight: scene.theme.fontweight ? scene.theme.fontweight : 'bold'
933         };
934
935         scene.text = scene.theme.text || Math.floor(scene.width) + 'x' + Math.floor(scene.height);
936
937         scene.noWrap = scene.theme.nowrap || scene.flags.nowrap;
938
939         scene.align = scene.theme.align || scene.flags.align || 'center';
940
941         switch (scene.flags.textmode) {
942             case 'literal':
943                 scene.text = scene.flags.dimensions.width + 'x' + scene.flags.dimensions.height;
944                 break;
945             case 'exact':
946                 if (!scene.flags.exactDimensions) break;
947                 scene.text = Math.floor(scene.flags.exactDimensions.width) + 'x' + Math.floor(scene.flags.exactDimensions.height);
948                 break;
949         }
950
951         var sceneGraph = new SceneGraph({
952             width: scene.width,
953             height: scene.height
954         });
955
956         var Shape = sceneGraph.Shape;
957
958         var holderBg = new Shape.Rect('holderBg', {
959             fill: scene.theme.background
960         });
961
962         holderBg.resize(scene.width, scene.height);
963         sceneGraph.root.add(holderBg);
964
965         if (scene.flags.outline) {
966             //todo: generalize darken/lighten to more than RRGGBB hex values
967             var outlineColor = new Color(holderBg.properties.fill);
968
969             outlineColor = outlineColor.lighten(outlineColor.lighterThan('7f7f7f') ? -0.1 : 0.1);
970
971             holderBg.properties.outline = {
972                 fill: outlineColor.toHex(true),
973                 width: 2
974             };
975         }
976
977         var holderTextColor = scene.theme.foreground;
978
979         if (scene.flags.autoFg) {
980             var holderBgColor = new Color(holderBg.properties.fill);
981             var lightColor = new Color('fff');
982             var darkColor = new Color('000', { 'alpha': 0.285714 });
983
984             holderTextColor = holderBgColor.blendAlpha(holderBgColor.lighterThan('7f7f7f') ? darkColor : lightColor).toHex(true);
985         }
986
987         var holderTextGroup = new Shape.Group('holderTextGroup', {
988             text: scene.text,
989             align: scene.align,
990             font: scene.font,
991             fill: holderTextColor
992         });
993
994         holderTextGroup.moveTo(null, null, 1);
995         sceneGraph.root.add(holderTextGroup);
996
997         var tpdata = holderTextGroup.textPositionData = stagingRenderer(sceneGraph);
998         if (!tpdata) {
999             throw 'Holder: staging fallback not supported yet.';
1000         }
1001         holderTextGroup.properties.leading = tpdata.boundingBox.height;
1002
1003         var textNode = null;
1004         var line = null;
1005
1006         function finalizeLine(parent, line, width, height) {
1007             line.width = width;
1008             line.height = height;
1009             parent.width = Math.max(parent.width, line.width);
1010             parent.height += line.height;
1011         }
1012
1013         var sceneMargin = scene.width * App.vars.lineWrapRatio;
1014         var maxLineWidth = sceneMargin;
1015
1016         if (tpdata.lineCount > 1) {
1017             var offsetX = 0;
1018             var offsetY = 0;
1019             var lineIndex = 0;
1020             var lineKey;
1021             line = new Shape.Group('line' + lineIndex);
1022
1023             //Double margin so that left/right-aligned next is not flush with edge of image
1024             if (scene.align === 'left' || scene.align === 'right') {
1025                 maxLineWidth = scene.width * (1 - (1 - (App.vars.lineWrapRatio)) * 2);
1026             }
1027
1028             for (var i = 0; i < tpdata.words.length; i++) {
1029                 var word = tpdata.words[i];
1030                 textNode = new Shape.Text(word.text);
1031                 var newline = word.text == '\\n';
1032                 if (!scene.noWrap && (offsetX + word.width >= maxLineWidth || newline === true)) {
1033                     finalizeLine(holderTextGroup, line, offsetX, holderTextGroup.properties.leading);
1034                     holderTextGroup.add(line);
1035                     offsetX = 0;
1036                     offsetY += holderTextGroup.properties.leading;
1037                     lineIndex += 1;
1038                     line = new Shape.Group('line' + lineIndex);
1039                     line.y = offsetY;
1040                 }
1041                 if (newline === true) {
1042                     continue;
1043                 }
1044                 textNode.moveTo(offsetX, 0);
1045                 offsetX += tpdata.spaceWidth + word.width;
1046                 line.add(textNode);
1047             }
1048
1049             finalizeLine(holderTextGroup, line, offsetX, holderTextGroup.properties.leading);
1050             holderTextGroup.add(line);
1051
1052             if (scene.align === 'left') {
1053                 holderTextGroup.moveTo(scene.width - sceneMargin, null, null);
1054             } else if (scene.align === 'right') {
1055                 for (lineKey in holderTextGroup.children) {
1056                     line = holderTextGroup.children[lineKey];
1057                     line.moveTo(scene.width - line.width, null, null);
1058                 }
1059
1060                 holderTextGroup.moveTo(0 - (scene.width - sceneMargin), null, null);
1061             } else {
1062                 for (lineKey in holderTextGroup.children) {
1063                     line = holderTextGroup.children[lineKey];
1064                     line.moveTo((holderTextGroup.width - line.width) / 2, null, null);
1065                 }
1066
1067                 holderTextGroup.moveTo((scene.width - holderTextGroup.width) / 2, null, null);
1068             }
1069
1070             holderTextGroup.moveTo(null, (scene.height - holderTextGroup.height) / 2, null);
1071
1072             //If the text exceeds vertical space, move it down so the first line is visible
1073             if ((scene.height - holderTextGroup.height) / 2 < 0) {
1074                 holderTextGroup.moveTo(null, 0, null);
1075             }
1076         } else {
1077             textNode = new Shape.Text(scene.text);
1078             line = new Shape.Group('line0');
1079             line.add(textNode);
1080             holderTextGroup.add(line);
1081
1082             if (scene.align === 'left') {
1083                 holderTextGroup.moveTo(scene.width - sceneMargin, null, null);
1084             } else if (scene.align === 'right') {
1085                 holderTextGroup.moveTo(0 - (scene.width - sceneMargin), null, null);
1086             } else {
1087                 holderTextGroup.moveTo((scene.width - tpdata.boundingBox.width) / 2, null, null);
1088             }
1089
1090             holderTextGroup.moveTo(null, (scene.height - tpdata.boundingBox.height) / 2, null);
1091         }
1092
1093         //todo: renderlist
1094         return sceneGraph;
1095     }
1096
1097     /**
1098      * Adaptive text sizing function
1099      *
1100      * @private
1101      * @param width Parent width
1102      * @param height Parent height
1103      * @param fontSize Requested text size
1104      */
1105     function textSize(width, height, fontSize) {
1106         var stageWidth = parseInt(width, 10);
1107         var stageHeight = parseInt(height, 10);
1108
1109         var bigSide = Math.max(stageWidth, stageHeight);
1110         var smallSide = Math.min(stageWidth, stageHeight);
1111
1112         var newHeight = 0.8 * Math.min(smallSide, bigSide * App.defaults.scale);
1113         return Math.round(Math.max(fontSize, newHeight));
1114     }
1115
1116     /**
1117      * Iterates over resizable (fluid or auto) placeholders and renders them
1118      *
1119      * @private
1120      * @param element Optional element selector, specified only if a specific element needs to be re-rendered
1121      */
1122     function updateResizableElements(element) {
1123         var images;
1124         if (element == null || element.nodeType == null) {
1125             images = App.vars.resizableImages;
1126         } else {
1127             images = [element];
1128         }
1129         for (var i = 0, l = images.length; i < l; i++) {
1130             var el = images[i];
1131             if (el.holderData) {
1132                 var flags = el.holderData.flags;
1133                 var dimensions = dimensionCheck(el);
1134                 if (dimensions) {
1135                     if (!el.holderData.resizeUpdate) {
1136                         continue;
1137                     }
1138
1139                     if (flags.fluid && flags.auto) {
1140                         var fluidConfig = el.holderData.fluidConfig;
1141                         switch (fluidConfig.mode) {
1142                             case 'width':
1143                                 dimensions.height = dimensions.width / fluidConfig.ratio;
1144                                 break;
1145                             case 'height':
1146                                 dimensions.width = dimensions.height * fluidConfig.ratio;
1147                                 break;
1148                         }
1149                     }
1150
1151                     var settings = {
1152                         mode: 'image',
1153                         holderSettings: {
1154                             dimensions: dimensions,
1155                             theme: flags.theme,
1156                             flags: flags
1157                         },
1158                         el: el,
1159                         engineSettings: el.holderData.engineSettings
1160                     };
1161
1162                     if (flags.textmode == 'exact') {
1163                         flags.exactDimensions = dimensions;
1164                         settings.holderSettings.dimensions = flags.dimensions;
1165                     }
1166
1167                     render(settings);
1168                 } else {
1169                     setInvisible(el);
1170                 }
1171             }
1172         }
1173     }
1174
1175     /**
1176      * Sets up aspect ratio metadata for fluid placeholders, in order to preserve proportions when resizing
1177      *
1178      * @private
1179      * @param el Image DOM element
1180      */
1181     function setInitialDimensions(el) {
1182         if (el.holderData) {
1183             var dimensions = dimensionCheck(el);
1184             if (dimensions) {
1185                 var flags = el.holderData.flags;
1186
1187                 var fluidConfig = {
1188                     fluidHeight: flags.dimensions.height.slice(-1) == '%',
1189                     fluidWidth: flags.dimensions.width.slice(-1) == '%',
1190                     mode: null,
1191                     initialDimensions: dimensions
1192                 };
1193
1194                 if (fluidConfig.fluidWidth && !fluidConfig.fluidHeight) {
1195                     fluidConfig.mode = 'width';
1196                     fluidConfig.ratio = fluidConfig.initialDimensions.width / parseFloat(flags.dimensions.height);
1197                 } else if (!fluidConfig.fluidWidth && fluidConfig.fluidHeight) {
1198                     fluidConfig.mode = 'height';
1199                     fluidConfig.ratio = parseFloat(flags.dimensions.width) / fluidConfig.initialDimensions.height;
1200                 }
1201
1202                 el.holderData.fluidConfig = fluidConfig;
1203             } else {
1204                 setInvisible(el);
1205             }
1206         }
1207     }
1208
1209     /**
1210      * Iterates through all current invisible images, and if they're visible, renders them and removes them from further checks. Runs every animation frame.
1211      *
1212      * @private
1213      */
1214     function visibilityCheck() {
1215         var renderableImages = [];
1216         var keys = Object.keys(App.vars.invisibleImages);
1217         var el;
1218         for (var i = 0, l = keys.length; i < l; i++) {
1219             el = App.vars.invisibleImages[keys[i]];
1220             if (dimensionCheck(el) && el.nodeName.toLowerCase() == 'img') {
1221                 renderableImages.push(el);
1222                 delete App.vars.invisibleImages[keys[i]];
1223             }
1224         }
1225
1226         if (renderableImages.length) {
1227             Holder.run({
1228                 images: renderableImages
1229             });
1230         }
1231
1232         global.requestAnimationFrame(visibilityCheck);
1233     }
1234
1235     /**
1236      * Starts checking for invisible placeholders if not doing so yet. Does nothing otherwise.
1237      *
1238      * @private
1239      */
1240     function startVisibilityCheck() {
1241         if (!App.vars.visibilityCheckStarted) {
1242             global.requestAnimationFrame(visibilityCheck);
1243             App.vars.visibilityCheckStarted = true;
1244         }
1245     }
1246
1247     /**
1248      * Sets a unique ID for an image detected to be invisible and adds it to the map of invisible images checked by visibilityCheck
1249      *
1250      * @private
1251      * @param el Invisible DOM element
1252      */
1253     function setInvisible(el) {
1254         if (!el.holderData.invisibleId) {
1255             App.vars.invisibleId += 1;
1256             App.vars.invisibleImages['i' + App.vars.invisibleId] = el;
1257             el.holderData.invisibleId = App.vars.invisibleId;
1258         }
1259     }
1260
1261     //todo: see if possible to convert stagingRenderer to use HTML only
1262     var stagingRenderer = (function() {
1263         var svg = null,
1264             stagingText = null,
1265             stagingTextNode = null;
1266         return function(graph) {
1267             var rootNode = graph.root;
1268             if (App.setup.supportsSVG) {
1269                 var firstTimeSetup = false;
1270                 var tnode = function(text) {
1271                     return document.createTextNode(text);
1272                 };
1273                 if (svg == null || svg.parentNode !== document.body) {
1274                     firstTimeSetup = true;
1275                 }
1276
1277                 svg = SVG.initSVG(svg, rootNode.properties.width, rootNode.properties.height);
1278                 //Show staging element before staging
1279                 svg.style.display = 'block';
1280
1281                 if (firstTimeSetup) {
1282                     stagingText = DOM.newEl('text', SVG_NS);
1283                     stagingTextNode = tnode(null);
1284                     DOM.setAttr(stagingText, {
1285                         x: 0
1286                     });
1287                     stagingText.appendChild(stagingTextNode);
1288                     svg.appendChild(stagingText);
1289                     document.body.appendChild(svg);
1290                     svg.style.visibility = 'hidden';
1291                     svg.style.position = 'absolute';
1292                     svg.style.top = '-100%';
1293                     svg.style.left = '-100%';
1294                     //todo: workaround for zero-dimension <svg> tag in Opera 12
1295                     //svg.setAttribute('width', 0);
1296                     //svg.setAttribute('height', 0);
1297                 }
1298
1299                 var holderTextGroup = rootNode.children.holderTextGroup;
1300                 var htgProps = holderTextGroup.properties;
1301                 DOM.setAttr(stagingText, {
1302                     'y': htgProps.font.size,
1303                     'style': utils.cssProps({
1304                         'font-weight': htgProps.font.weight,
1305                         'font-size': htgProps.font.size + htgProps.font.units,
1306                         'font-family': htgProps.font.family
1307                     })
1308                 });
1309
1310                 //Get bounding box for the whole string (total width and height)
1311                 stagingTextNode.nodeValue = htgProps.text;
1312                 var stagingTextBBox = stagingText.getBBox();
1313
1314                 //Get line count and split the string into words
1315                 var lineCount = Math.ceil(stagingTextBBox.width / (rootNode.properties.width * App.vars.lineWrapRatio));
1316                 var words = htgProps.text.split(' ');
1317                 var newlines = htgProps.text.match(/\\n/g);
1318                 lineCount += newlines == null ? 0 : newlines.length;
1319
1320                 //Get bounding box for the string with spaces removed
1321                 stagingTextNode.nodeValue = htgProps.text.replace(/[ ]+/g, '');
1322                 var computedNoSpaceLength = stagingText.getComputedTextLength();
1323
1324                 //Compute average space width
1325                 var diffLength = stagingTextBBox.width - computedNoSpaceLength;
1326                 var spaceWidth = Math.round(diffLength / Math.max(1, words.length - 1));
1327
1328                 //Get widths for every word with space only if there is more than one line
1329                 var wordWidths = [];
1330                 if (lineCount > 1) {
1331                     stagingTextNode.nodeValue = '';
1332                     for (var i = 0; i < words.length; i++) {
1333                         if (words[i].length === 0) continue;
1334                         stagingTextNode.nodeValue = utils.decodeHtmlEntity(words[i]);
1335                         var bbox = stagingText.getBBox();
1336                         wordWidths.push({
1337                             text: words[i],
1338                             width: bbox.width
1339                         });
1340                     }
1341                 }
1342
1343                 //Hide staging element after staging
1344                 svg.style.display = 'none';
1345
1346                 return {
1347                     spaceWidth: spaceWidth,
1348                     lineCount: lineCount,
1349                     boundingBox: stagingTextBBox,
1350                     words: wordWidths
1351                 };
1352             } else {
1353                 //todo: canvas fallback for measuring text on android 2.3
1354                 return false;
1355             }
1356         };
1357     })();
1358
1359     var sgCanvasRenderer = (function() {
1360         var canvas = DOM.newEl('canvas');
1361         var ctx = null;
1362
1363         return function(sceneGraph) {
1364             if (ctx == null) {
1365                 ctx = canvas.getContext('2d');
1366             }
1367             var root = sceneGraph.root;
1368             canvas.width = App.dpr(root.properties.width);
1369             canvas.height = App.dpr(root.properties.height);
1370             ctx.textBaseline = 'middle';
1371
1372             var bg = root.children.holderBg;
1373             var bgWidth = App.dpr(bg.width);
1374             var bgHeight = App.dpr(bg.height);
1375             //todo: parametrize outline width (e.g. in scene object)
1376             var outlineWidth = 2;
1377             var outlineOffsetWidth = outlineWidth / 2;
1378
1379             ctx.fillStyle = bg.properties.fill;
1380             ctx.fillRect(0, 0, bgWidth, bgHeight);
1381
1382             if (bg.properties.outline) {
1383                 //todo: abstract this into a method
1384                 ctx.strokeStyle = bg.properties.outline.fill;
1385                 ctx.lineWidth = bg.properties.outline.width;
1386                 ctx.moveTo(outlineOffsetWidth, outlineOffsetWidth);
1387                 // TL, TR, BR, BL
1388                 ctx.lineTo(bgWidth - outlineOffsetWidth, outlineOffsetWidth);
1389                 ctx.lineTo(bgWidth - outlineOffsetWidth, bgHeight - outlineOffsetWidth);
1390                 ctx.lineTo(outlineOffsetWidth, bgHeight - outlineOffsetWidth);
1391                 ctx.lineTo(outlineOffsetWidth, outlineOffsetWidth);
1392                 // Diagonals
1393                 ctx.moveTo(0, outlineOffsetWidth);
1394                 ctx.lineTo(bgWidth, bgHeight - outlineOffsetWidth);
1395                 ctx.moveTo(0, bgHeight - outlineOffsetWidth);
1396                 ctx.lineTo(bgWidth, outlineOffsetWidth);
1397                 ctx.stroke();
1398             }
1399
1400             var textGroup = root.children.holderTextGroup;
1401             var tgProps = textGroup.properties;
1402             ctx.font = textGroup.properties.font.weight + ' ' + App.dpr(textGroup.properties.font.size) + textGroup.properties.font.units + ' ' + textGroup.properties.font.family + ', monospace';
1403             ctx.fillStyle = textGroup.properties.fill;
1404
1405             for (var lineKey in textGroup.children) {
1406                 var line = textGroup.children[lineKey];
1407                 for (var wordKey in line.children) {
1408                     var word = line.children[wordKey];
1409                     var x = App.dpr(textGroup.x + line.x + word.x);
1410                     var y = App.dpr(textGroup.y + line.y + word.y + (textGroup.properties.leading / 2));
1411
1412                     ctx.fillText(word.properties.text, x, y);
1413                 }
1414             }
1415
1416             return canvas.toDataURL('image/png');
1417         };
1418     })();
1419
1420     var sgSVGRenderer = (function() {
1421         //Prevent IE <9 from initializing SVG renderer
1422         if (!global.XMLSerializer) return;
1423         var xml = DOM.createXML();
1424         var svg = SVG.initSVG(null, 0, 0);
1425         var bgEl = DOM.newEl('rect', SVG_NS);
1426         svg.appendChild(bgEl);
1427
1428         //todo: create a reusable pool for textNodes, resize if more words present
1429
1430         return function(sceneGraph, renderSettings) {
1431             var root = sceneGraph.root;
1432
1433             SVG.initSVG(svg, root.properties.width, root.properties.height);
1434
1435             var groups = svg.querySelectorAll('g');
1436
1437             for (var i = 0; i < groups.length; i++) {
1438                 groups[i].parentNode.removeChild(groups[i]);
1439             }
1440
1441             var holderURL = renderSettings.holderSettings.flags.holderURL;
1442             var holderId = 'holder_' + (Number(new Date()) + 32768 + (0 | Math.random() * 32768)).toString(16);
1443             var sceneGroupEl = DOM.newEl('g', SVG_NS);
1444             var textGroup = root.children.holderTextGroup;
1445             var tgProps = textGroup.properties;
1446             var textGroupEl = DOM.newEl('g', SVG_NS);
1447             var tpdata = textGroup.textPositionData;
1448             var textCSSRule = '#' + holderId + ' text { ' +
1449                 utils.cssProps({
1450                     'fill': tgProps.fill,
1451                     'font-weight': tgProps.font.weight,
1452                     'font-family': tgProps.font.family + ', monospace',
1453                     'font-size': tgProps.font.size + tgProps.font.units
1454                 }) + ' } ';
1455             var commentNode = xml.createComment('\n' + 'Source URL: ' + holderURL + generatorComment);
1456             var holderCSS = xml.createCDATASection(textCSSRule);
1457             var styleEl = svg.querySelector('style');
1458             var bg = root.children.holderBg;
1459
1460             DOM.setAttr(sceneGroupEl, {
1461                 id: holderId
1462             });
1463
1464             svg.insertBefore(commentNode, svg.firstChild);
1465             styleEl.appendChild(holderCSS);
1466
1467             sceneGroupEl.appendChild(bgEl);
1468
1469             //todo: abstract this into a cross-browser SVG outline method
1470             if (bg.properties.outline) {
1471                 var outlineEl = DOM.newEl('path', SVG_NS);
1472                 var outlineWidth = bg.properties.outline.width;
1473                 var outlineOffsetWidth = outlineWidth / 2;
1474                 DOM.setAttr(outlineEl, {
1475                     'd': [
1476                         'M', outlineOffsetWidth, outlineOffsetWidth,
1477                         'H', bg.width - outlineOffsetWidth,
1478                         'V', bg.height - outlineOffsetWidth,
1479                         'H', outlineOffsetWidth,
1480                         'V', 0,
1481                         'M', 0, outlineOffsetWidth,
1482                         'L', bg.width, bg.height - outlineOffsetWidth,
1483                         'M', 0, bg.height - outlineOffsetWidth,
1484                         'L', bg.width, outlineOffsetWidth
1485                     ].join(' '),
1486                     'stroke-width': bg.properties.outline.width,
1487                     'stroke': bg.properties.outline.fill,
1488                     'fill': 'none'
1489                 });
1490                 sceneGroupEl.appendChild(outlineEl);
1491             }
1492
1493             sceneGroupEl.appendChild(textGroupEl);
1494             svg.appendChild(sceneGroupEl);
1495
1496             DOM.setAttr(bgEl, {
1497                 'width': bg.width,
1498                 'height': bg.height,
1499                 'fill': bg.properties.fill
1500             });
1501
1502             textGroup.y += tpdata.boundingBox.height * 0.8;
1503
1504             for (var lineKey in textGroup.children) {
1505                 var line = textGroup.children[lineKey];
1506                 for (var wordKey in line.children) {
1507                     var word = line.children[wordKey];
1508                     var x = textGroup.x + line.x + word.x;
1509                     var y = textGroup.y + line.y + word.y;
1510
1511                     var textEl = DOM.newEl('text', SVG_NS);
1512                     var textNode = document.createTextNode(null);
1513
1514                     DOM.setAttr(textEl, {
1515                         'x': x,
1516                         'y': y
1517                     });
1518
1519                     textNode.nodeValue = word.properties.text;
1520                     textEl.appendChild(textNode);
1521                     textGroupEl.appendChild(textEl);
1522                 }
1523             }
1524
1525             //todo: factor the background check up the chain, perhaps only return reference
1526             var svgString = SVG.svgStringToDataURI(SVG.serializeSVG(svg, renderSettings.engineSettings), renderSettings.mode === 'background');
1527             return svgString;
1528         };
1529     })();
1530
1531     //Helpers
1532
1533     /**
1534      * Prevents a function from being called too often, waits until a timer elapses to call it again
1535      *
1536      * @param fn Function to call
1537      */
1538     function debounce(fn) {
1539         if (!App.vars.debounceTimer) fn.call(this);
1540         if (App.vars.debounceTimer) global.clearTimeout(App.vars.debounceTimer);
1541         App.vars.debounceTimer = global.setTimeout(function() {
1542             App.vars.debounceTimer = null;
1543             fn.call(this);
1544         }, App.setup.debounce);
1545     }
1546
1547     /**
1548      * Holder-specific resize/orientation change callback, debounced to prevent excessive execution
1549      */
1550     function resizeEvent() {
1551         debounce(function() {
1552             updateResizableElements(null);
1553         });
1554     }
1555
1556     //Set up flags
1557
1558     for (var flag in App.flags) {
1559         if (!App.flags.hasOwnProperty(flag)) continue;
1560         App.flags[flag].match = function(val) {
1561             return val.match(this.regex);
1562         };
1563     }
1564
1565     //Properties set once on setup
1566
1567     App.setup = {
1568         renderer: 'html',
1569         debounce: 100,
1570         ratio: 1,
1571         supportsCanvas: false,
1572         supportsSVG: false,
1573         lineWrapRatio: 0.9,
1574         dataAttr: 'data-src',
1575         renderers: ['html', 'canvas', 'svg']
1576     };
1577
1578     App.dpr = function(val) {
1579         return val * App.setup.ratio;
1580     };
1581
1582     //Properties modified during runtime
1583
1584     App.vars = {
1585         preempted: false,
1586         resizableImages: [],
1587         invisibleImages: {},
1588         invisibleId: 0,
1589         visibilityCheckStarted: false,
1590         debounceTimer: null,
1591         cache: {}
1592     };
1593
1594     //Pre-flight
1595
1596     (function() {
1597         var devicePixelRatio = 1,
1598             backingStoreRatio = 1;
1599
1600         var canvas = DOM.newEl('canvas');
1601         var ctx = null;
1602
1603         if (canvas.getContext) {
1604             if (canvas.toDataURL('image/png').indexOf('data:image/png') != -1) {
1605                 App.setup.renderer = 'canvas';
1606                 ctx = canvas.getContext('2d');
1607                 App.setup.supportsCanvas = true;
1608             }
1609         }
1610
1611         if (App.setup.supportsCanvas) {
1612             devicePixelRatio = global.devicePixelRatio || 1;
1613             backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
1614         }
1615
1616         App.setup.ratio = devicePixelRatio / backingStoreRatio;
1617
1618         if (!!document.createElementNS && !!document.createElementNS(SVG_NS, 'svg').createSVGRect) {
1619             App.setup.renderer = 'svg';
1620             App.setup.supportsSVG = true;
1621         }
1622     })();
1623
1624     //Starts checking for invisible placeholders
1625     startVisibilityCheck();
1626
1627     if (onDomReady) {
1628         onDomReady(function() {
1629             if (!App.vars.preempted) {
1630                 Holder.run();
1631             }
1632             if (global.addEventListener) {
1633                 global.addEventListener('resize', resizeEvent, false);
1634                 global.addEventListener('orientationchange', resizeEvent, false);
1635             } else {
1636                 global.attachEvent('onresize', resizeEvent);
1637             }
1638
1639             if (typeof global.Turbolinks == 'object') {
1640                 global.document.addEventListener('page:change', function() {
1641                     Holder.run();
1642                 });
1643             }
1644         });
1645     }
1646
1647     module.exports = Holder;
1648
1649     /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
1650
1651 /***/ },
1652 /* 2 */
1653 /***/ function(module, exports, __webpack_require__) {
1654
1655     //Modified version of component/querystring
1656     //Changes: updated dependencies, dot notation parsing, JSHint fixes
1657     //Fork at https://github.com/imsky/querystring
1658
1659     /**
1660      * Module dependencies.
1661      */
1662
1663     var encode = encodeURIComponent;
1664     var decode = decodeURIComponent;
1665     var trim = __webpack_require__(10);
1666     var type = __webpack_require__(9);
1667
1668     var arrayRegex = /(\w+)\[(\d+)\]/;
1669     var objectRegex = /\w+\.\w+/;
1670
1671     /**
1672      * Parse the given query `str`.
1673      *
1674      * @param {String} str
1675      * @return {Object}
1676      * @api public
1677      */
1678
1679     exports.parse = function(str){
1680       if ('string' !== typeof str) return {};
1681
1682       str = trim(str);
1683       if ('' === str) return {};
1684       if ('?' === str.charAt(0)) str = str.slice(1);
1685
1686       var obj = {};
1687       var pairs = str.split('&');
1688       for (var i = 0; i < pairs.length; i++) {
1689         var parts = pairs[i].split('=');
1690         var key = decode(parts[0]);
1691         var m, ctx, prop;
1692
1693         if (m = arrayRegex.exec(key)) {
1694           obj[m[1]] = obj[m[1]] || [];
1695           obj[m[1]][m[2]] = decode(parts[1]);
1696           continue;
1697         }
1698
1699         if (m = objectRegex.test(key)) {
1700           m = key.split('.');
1701           ctx = obj;
1702           
1703           while (m.length) {
1704             prop = m.shift();
1705
1706             if (!prop.length) continue;
1707
1708             if (!ctx[prop]) {
1709               ctx[prop] = {};
1710             } else if (ctx[prop] && typeof ctx[prop] !== 'object') {
1711               break;
1712             }
1713
1714             if (!m.length) {
1715               ctx[prop] = decode(parts[1]);
1716             }
1717
1718             ctx = ctx[prop];
1719           }
1720
1721           continue;
1722         }
1723
1724         obj[parts[0]] = null == parts[1] ? '' : decode(parts[1]);
1725       }
1726
1727       return obj;
1728     };
1729
1730     /**
1731      * Stringify the given `obj`.
1732      *
1733      * @param {Object} obj
1734      * @return {String}
1735      * @api public
1736      */
1737
1738     exports.stringify = function(obj){
1739       if (!obj) return '';
1740       var pairs = [];
1741
1742       for (var key in obj) {
1743         var value = obj[key];
1744
1745         if ('array' == type(value)) {
1746           for (var i = 0; i < value.length; ++i) {
1747             pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i]));
1748           }
1749           continue;
1750         }
1751
1752         pairs.push(encode(key) + '=' + encode(obj[key]));
1753       }
1754
1755       return pairs.join('&');
1756     };
1757
1758
1759 /***/ },
1760 /* 3 */
1761 /***/ function(module, exports, __webpack_require__) {
1762
1763     /*!
1764      * onDomReady.js 1.4.0 (c) 2013 Tubal Martin - MIT license
1765      *
1766      * Specially modified to work with Holder.js
1767      */
1768
1769     function _onDomReady(win) {
1770         //Lazy loading fix for Firefox < 3.6
1771         //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
1772         if (document.readyState == null && document.addEventListener) {
1773             document.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
1774                 document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
1775                 document.readyState = "complete";
1776             }, false);
1777             document.readyState = "loading";
1778         }
1779         
1780         var doc = win.document,
1781             docElem = doc.documentElement,
1782         
1783             LOAD = "load",
1784             FALSE = false,
1785             ONLOAD = "on"+LOAD,
1786             COMPLETE = "complete",
1787             READYSTATE = "readyState",
1788             ATTACHEVENT = "attachEvent",
1789             DETACHEVENT = "detachEvent",
1790             ADDEVENTLISTENER = "addEventListener",
1791             DOMCONTENTLOADED = "DOMContentLoaded",
1792             ONREADYSTATECHANGE = "onreadystatechange",
1793             REMOVEEVENTLISTENER = "removeEventListener",
1794         
1795             // W3C Event model
1796             w3c = ADDEVENTLISTENER in doc,
1797             _top = FALSE,
1798         
1799             // isReady: Is the DOM ready to be used? Set to true once it occurs.
1800             isReady = FALSE,
1801         
1802             // Callbacks pending execution until DOM is ready
1803             callbacks = [];
1804         
1805         // Handle when the DOM is ready
1806         function ready( fn ) {
1807             if ( !isReady ) {
1808         
1809                 // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
1810                 if ( !doc.body ) {
1811                     return defer( ready );
1812                 }
1813         
1814                 // Remember that the DOM is ready
1815                 isReady = true;
1816         
1817                 // Execute all callbacks
1818                 while ( fn = callbacks.shift() ) {
1819                     defer( fn );
1820                 }
1821             }
1822         }
1823         
1824         // The ready event handler
1825         function completed( event ) {
1826             // readyState === "complete" is good enough for us to call the dom ready in oldIE
1827             if ( w3c || event.type === LOAD || doc[READYSTATE] === COMPLETE ) {
1828                 detach();
1829                 ready();
1830             }
1831         }
1832         
1833         // Clean-up method for dom ready events
1834         function detach() {
1835             if ( w3c ) {
1836                 doc[REMOVEEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE );
1837                 win[REMOVEEVENTLISTENER]( LOAD, completed, FALSE );
1838             } else {
1839                 doc[DETACHEVENT]( ONREADYSTATECHANGE, completed );
1840                 win[DETACHEVENT]( ONLOAD, completed );
1841             }
1842         }
1843         
1844         // Defers a function, scheduling it to run after the current call stack has cleared.
1845         function defer( fn, wait ) {
1846             // Allow 0 to be passed
1847             setTimeout( fn, +wait >= 0 ? wait : 1 );
1848         }
1849         
1850         // Attach the listeners:
1851         
1852         // Catch cases where onDomReady is called after the browser event has already occurred.
1853         // we once tried to use readyState "interactive" here, but it caused issues like the one
1854         // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
1855         if ( doc[READYSTATE] === COMPLETE ) {
1856             // Handle it asynchronously to allow scripts the opportunity to delay ready
1857             defer( ready );
1858         
1859         // Standards-based browsers support DOMContentLoaded
1860         } else if ( w3c ) {
1861             // Use the handy event callback
1862             doc[ADDEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE );
1863         
1864             // A fallback to window.onload, that will always work
1865             win[ADDEVENTLISTENER]( LOAD, completed, FALSE );
1866         
1867         // If IE event model is used
1868         } else {
1869             // Ensure firing before onload, maybe late but safe also for iframes
1870             doc[ATTACHEVENT]( ONREADYSTATECHANGE, completed );
1871         
1872             // A fallback to window.onload, that will always work
1873             win[ATTACHEVENT]( ONLOAD, completed );
1874         
1875             // If IE and not a frame
1876             // continually check to see if the document is ready
1877             try {
1878                 _top = win.frameElement == null && docElem;
1879             } catch(e) {}
1880         
1881             if ( _top && _top.doScroll ) {
1882                 (function doScrollCheck() {
1883                     if ( !isReady ) {
1884                         try {
1885                             // Use the trick by Diego Perini
1886                             // http://javascript.nwbox.com/IEContentLoaded/
1887                             _top.doScroll("left");
1888                         } catch(e) {
1889                             return defer( doScrollCheck, 50 );
1890                         }
1891         
1892                         // detach all dom ready events
1893                         detach();
1894         
1895                         // and execute any waiting functions
1896                         ready();
1897                     }
1898                 })();
1899             }
1900         }
1901         
1902         function onDomReady( fn ) {
1903             // If DOM is ready, execute the function (async), otherwise wait
1904             isReady ? defer( fn ) : callbacks.push( fn );
1905         }
1906         
1907         // Add version
1908         onDomReady.version = "1.4.0";
1909         // Add method to check if DOM is ready
1910         onDomReady.isReady = function(){
1911             return isReady;
1912         };
1913
1914         return onDomReady;
1915     }
1916
1917     module.exports = typeof window !== "undefined" && _onDomReady(window);
1918
1919 /***/ },
1920 /* 4 */
1921 /***/ function(module, exports, __webpack_require__) {
1922
1923     var SceneGraph = function(sceneProperties) {
1924         var nodeCount = 1;
1925
1926         //todo: move merge to helpers section
1927         function merge(parent, child) {
1928             for (var prop in child) {
1929                 parent[prop] = child[prop];
1930             }
1931             return parent;
1932         }
1933
1934         var SceneNode = function(name) {
1935             nodeCount++;
1936             this.parent = null;
1937             this.children = {};
1938             this.id = nodeCount;
1939             this.name = 'n' + nodeCount;
1940             if (typeof name !== 'undefined') {
1941                 this.name = name;
1942             }
1943             this.x = this.y = this.z = 0;
1944             this.width = this.height = 0;
1945         };
1946
1947         SceneNode.prototype.resize = function(width, height) {
1948             if (width != null) {
1949                 this.width = width;
1950             }
1951             if (height != null) {
1952                 this.height = height;
1953             }
1954         };
1955
1956         SceneNode.prototype.moveTo = function(x, y, z) {
1957             this.x = x != null ? x : this.x;
1958             this.y = y != null ? y : this.y;
1959             this.z = z != null ? z : this.z;
1960         };
1961
1962         SceneNode.prototype.add = function(child) {
1963             var name = child.name;
1964             if (typeof this.children[name] === 'undefined') {
1965                 this.children[name] = child;
1966                 child.parent = this;
1967             } else {
1968                 throw 'SceneGraph: child already exists: ' + name;
1969             }
1970         };
1971
1972         var RootNode = function() {
1973             SceneNode.call(this, 'root');
1974             this.properties = sceneProperties;
1975         };
1976
1977         RootNode.prototype = new SceneNode();
1978
1979         var Shape = function(name, props) {
1980             SceneNode.call(this, name);
1981             this.properties = {
1982                 'fill': '#000000'
1983             };
1984             if (typeof props !== 'undefined') {
1985                 merge(this.properties, props);
1986             } else if (typeof name !== 'undefined' && typeof name !== 'string') {
1987                 throw 'SceneGraph: invalid node name';
1988             }
1989         };
1990
1991         Shape.prototype = new SceneNode();
1992
1993         var Group = function() {
1994             Shape.apply(this, arguments);
1995             this.type = 'group';
1996         };
1997
1998         Group.prototype = new Shape();
1999
2000         var Rect = function() {
2001             Shape.apply(this, arguments);
2002             this.type = 'rect';
2003         };
2004
2005         Rect.prototype = new Shape();
2006
2007         var Text = function(text) {
2008             Shape.call(this);
2009             this.type = 'text';
2010             this.properties.text = text;
2011         };
2012
2013         Text.prototype = new Shape();
2014
2015         var root = new RootNode();
2016
2017         this.Shape = {
2018             'Rect': Rect,
2019             'Text': Text,
2020             'Group': Group
2021         };
2022
2023         this.root = root;
2024         return this;
2025     };
2026
2027     module.exports = SceneGraph;
2028
2029
2030 /***/ },
2031 /* 5 */
2032 /***/ function(module, exports, __webpack_require__) {
2033
2034     /**
2035      * Shallow object clone and merge
2036      *
2037      * @param a Object A
2038      * @param b Object B
2039      * @returns {Object} New object with all of A's properties, and all of B's properties, overwriting A's properties
2040      */
2041     exports.extend = function(a, b) {
2042         var c = {};
2043         for (var x in a) {
2044             if (a.hasOwnProperty(x)) {
2045                 c[x] = a[x];
2046             }
2047         }
2048         if (b != null) {
2049             for (var y in b) {
2050                 if (b.hasOwnProperty(y)) {
2051                     c[y] = b[y];
2052                 }
2053             }
2054         }
2055         return c;
2056     };
2057
2058     /**
2059      * Takes a k/v list of CSS properties and returns a rule
2060      *
2061      * @param props CSS properties object
2062      */
2063     exports.cssProps = function(props) {
2064         var ret = [];
2065         for (var p in props) {
2066             if (props.hasOwnProperty(p)) {
2067                 ret.push(p + ':' + props[p]);
2068             }
2069         }
2070         return ret.join(';');
2071     };
2072
2073     /**
2074      * Encodes HTML entities in a string
2075      *
2076      * @param str Input string
2077      */
2078     exports.encodeHtmlEntity = function(str) {
2079         var buf = [];
2080         var charCode = 0;
2081         for (var i = str.length - 1; i >= 0; i--) {
2082             charCode = str.charCodeAt(i);
2083             if (charCode > 128) {
2084                 buf.unshift(['&#', charCode, ';'].join(''));
2085             } else {
2086                 buf.unshift(str[i]);
2087             }
2088         }
2089         return buf.join('');
2090     };
2091
2092     /**
2093      * Checks if an image exists
2094      *
2095      * @param src URL of image
2096      * @param callback Callback to call once image status has been found
2097      */
2098     exports.imageExists = function(src, callback) {
2099         var image = new Image();
2100         image.onerror = function() {
2101             callback.call(this, false);
2102         };
2103         image.onload = function() {
2104             callback.call(this, true);
2105         };
2106         image.src = src;
2107     };
2108
2109     /**
2110      * Decodes HTML entities in a string
2111      *
2112      * @param str Input string
2113      */
2114     exports.decodeHtmlEntity = function(str) {
2115         return str.replace(/&#(\d+);/g, function(match, dec) {
2116             return String.fromCharCode(dec);
2117         });
2118     };
2119
2120
2121     /**
2122      * Returns an element's dimensions if it's visible, `false` otherwise.
2123      *
2124      * @param el DOM element
2125      */
2126     exports.dimensionCheck = function(el) {
2127         var dimensions = {
2128             height: el.clientHeight,
2129             width: el.clientWidth
2130         };
2131
2132         if (dimensions.height && dimensions.width) {
2133             return dimensions;
2134         } else {
2135             return false;
2136         }
2137     };
2138
2139
2140     /**
2141      * Returns true if value is truthy or if it is "semantically truthy"
2142      * @param val
2143      */
2144     exports.truthy = function(val) {
2145         if (typeof val === 'string') {
2146             return val === 'true' || val === 'yes' || val === '1' || val === 'on' || val === '✓';
2147         }
2148         return !!val;
2149     };
2150
2151
2152 /***/ },
2153 /* 6 */
2154 /***/ function(module, exports, __webpack_require__) {
2155
2156     /* WEBPACK VAR INJECTION */(function(global) {var DOM = __webpack_require__(7);
2157
2158     var SVG_NS = 'http://www.w3.org/2000/svg';
2159     var NODE_TYPE_COMMENT = 8;
2160
2161     /**
2162      * Generic SVG element creation function
2163      *
2164      * @private
2165      * @param svg SVG context, set to null if new
2166      * @param width Document width
2167      * @param height Document height
2168      */
2169     exports.initSVG = function(svg, width, height) {
2170         var defs, style, initialize = false;
2171
2172         if (svg && svg.querySelector) {
2173             style = svg.querySelector('style');
2174             if (style === null) {
2175                 initialize = true;
2176             }
2177         } else {
2178             svg = DOM.newEl('svg', SVG_NS);
2179             initialize = true;
2180         }
2181
2182         if (initialize) {
2183             defs = DOM.newEl('defs', SVG_NS);
2184             style = DOM.newEl('style', SVG_NS);
2185             DOM.setAttr(style, {
2186                 'type': 'text/css'
2187             });
2188             defs.appendChild(style);
2189             svg.appendChild(defs);
2190         }
2191
2192         //IE throws an exception if this is set and Chrome requires it to be set
2193         if (svg.webkitMatchesSelector) {
2194             svg.setAttribute('xmlns', SVG_NS);
2195         }
2196
2197         //Remove comment nodes
2198         for (var i = 0; i < svg.childNodes.length; i++) {
2199             if (svg.childNodes[i].nodeType === NODE_TYPE_COMMENT) {
2200                 svg.removeChild(svg.childNodes[i]);
2201             }
2202         }
2203
2204         //Remove CSS
2205         while (style.childNodes.length) {
2206             style.removeChild(style.childNodes[0]);
2207         }
2208
2209         DOM.setAttr(svg, {
2210             'width': width,
2211             'height': height,
2212             'viewBox': '0 0 ' + width + ' ' + height,
2213             'preserveAspectRatio': 'none'
2214         });
2215
2216         return svg;
2217     };
2218
2219     /**
2220      * Converts serialized SVG to a string suitable for data URI use
2221      * @param svgString Serialized SVG string
2222      * @param [base64] Use base64 encoding for data URI
2223      */
2224     exports.svgStringToDataURI = function() {
2225         var rawPrefix = 'data:image/svg+xml;charset=UTF-8,';
2226         var base64Prefix = 'data:image/svg+xml;charset=UTF-8;base64,';
2227
2228         return function(svgString, base64) {
2229             if (base64) {
2230                 return base64Prefix + btoa(unescape(encodeURIComponent(svgString)));
2231             } else {
2232                 return rawPrefix + encodeURIComponent(svgString);
2233             }
2234         };
2235     }();
2236
2237     /**
2238      * Returns serialized SVG with XML processing instructions
2239      *
2240      * @private
2241      * @param svg SVG context
2242      * @param stylesheets CSS stylesheets to include
2243      */
2244     exports.serializeSVG = function(svg, engineSettings) {
2245         if (!global.XMLSerializer) return;
2246         var serializer = new XMLSerializer();
2247         var svgCSS = '';
2248         var stylesheets = engineSettings.stylesheets;
2249
2250         //External stylesheets: Processing Instruction method
2251         if (engineSettings.svgXMLStylesheet) {
2252             var xml = DOM.createXML();
2253             //Add <?xml-stylesheet ?> directives
2254             for (var i = stylesheets.length - 1; i >= 0; i--) {
2255                 var csspi = xml.createProcessingInstruction('xml-stylesheet', 'href="' + stylesheets[i] + '" rel="stylesheet"');
2256                 xml.insertBefore(csspi, xml.firstChild);
2257             }
2258
2259             xml.removeChild(xml.documentElement);
2260             svgCSS = serializer.serializeToString(xml);
2261         }
2262
2263         var svgText = serializer.serializeToString(svg);
2264         svgText = svgText.replace(/\&amp;(\#[0-9]{2,}\;)/g, '&$1');
2265         return svgCSS + svgText;
2266     };
2267
2268     /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2269
2270 /***/ },
2271 /* 7 */
2272 /***/ function(module, exports, __webpack_require__) {
2273
2274     /* WEBPACK VAR INJECTION */(function(global) {/**
2275      * Generic new DOM element function
2276      *
2277      * @private
2278      * @param tag Tag to create
2279      * @param namespace Optional namespace value
2280      */
2281     exports.newEl = function(tag, namespace) {
2282         if (!global.document) return;
2283
2284         if (namespace == null) {
2285             return document.createElement(tag);
2286         } else {
2287             return document.createElementNS(namespace, tag);
2288         }
2289     };
2290
2291     /**
2292      * Generic setAttribute function
2293      *
2294      * @private
2295      * @param el Reference to DOM element
2296      * @param attrs Object with attribute keys and values
2297      */
2298     exports.setAttr = function(el, attrs) {
2299         for (var a in attrs) {
2300             el.setAttribute(a, attrs[a]);
2301         }
2302     };
2303
2304     /**
2305      * Creates a XML document
2306      * @private
2307      */
2308     exports.createXML = function() {
2309         if (!global.DOMParser) return;
2310         return new DOMParser().parseFromString('<xml />', 'application/xml');
2311     };
2312
2313     /**
2314      * Converts a value into an array of DOM nodes
2315      *
2316      * @param val A string, a NodeList, a Node, or an HTMLCollection
2317      */
2318     exports.getNodeArray = function(val) {
2319         var retval = null;
2320         if (typeof(val) == 'string') {
2321             retval = document.querySelectorAll(val);
2322         } else if (global.NodeList && val instanceof global.NodeList) {
2323             retval = val;
2324         } else if (global.Node && val instanceof global.Node) {
2325             retval = [val];
2326         } else if (global.HTMLCollection && val instanceof global.HTMLCollection) {
2327             retval = val;
2328         } else if (val instanceof Array) {
2329             retval = val;
2330         } else if (val === null) {
2331             retval = [];
2332         }
2333         return retval;
2334     };
2335
2336     /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2337
2338 /***/ },
2339 /* 8 */
2340 /***/ function(module, exports, __webpack_require__) {
2341
2342     var Color = function (color, options) {
2343         //todo: support array->color conversion
2344         //todo: support rgba, hsla, and rrggbbaa notation
2345         if (typeof color !== 'string') return;
2346
2347         if (color.charAt(0) === '#') {
2348             color = color.slice(1);
2349         }
2350
2351         if (/[^a-f0-9]+/i.test(color)) return;
2352
2353         if (color.length === 3) {
2354             color = color.replace(/./g, '$&$&');
2355         }
2356
2357         if (color.length !== 6) return;
2358
2359         this.alpha = 1;
2360
2361         if (options) {
2362             this.alpha = options.alpha || this.alpha;
2363         }
2364
2365         colorSet.call(this, parseInt(color, 16));
2366     };
2367
2368     Color.rgbToHex = function (r, g, b) {
2369         return (((r | 0) << 16) + ((g | 0) << 8) + (b | 0)).toString(16);
2370     };
2371
2372     /**
2373      * Sets the color from a raw RGB888 integer
2374      * @param raw RGB888 representation of color
2375      */
2376      //todo: refactor into a more generic method
2377     function colorSet (raw) {
2378         this.rgb = {};
2379         this.yuv = {};
2380         this.raw = raw;
2381
2382         this.rgb.r = (raw & 0xFF0000) >> 16;
2383         this.rgb.g = (raw & 0x00FF00) >> 8;
2384         this.rgb.b = (raw & 0x0000FF);
2385
2386         // BT.709
2387         this.yuv.y = 0.2126 * this.rgb.r + 0.7152 * this.rgb.g + 0.0722 * this.rgb.b;
2388         this.yuv.u = -0.09991 * this.rgb.r - 0.33609 * this.rgb.g + 0.436 * this.rgb.b;
2389         this.yuv.v = 0.615 * this.rgb.r - 0.55861 * this.rgb.g - 0.05639 * this.rgb.b;
2390
2391         return this;
2392     }
2393
2394     /**
2395      * Lighten or darken a color
2396      * @param multiplier Amount to lighten or darken (-1 to 1)
2397      */
2398     Color.prototype.lighten = function (multiplier) {
2399         var r = this.rgb.r;
2400         var g = this.rgb.g;
2401         var b = this.rgb.b;
2402
2403         var m = (255 * multiplier) | 0;
2404
2405         return new Color(Color.rgbToHex(r + m, g + m, b + m));
2406     };
2407
2408     /**
2409      * Output color in hex format
2410      * @param addHash Add a hash character to the beginning of the output
2411      */ 
2412     Color.prototype.toHex = function (addHash) {
2413         return (addHash ? '#' : '') + this.raw.toString(16);
2414     };
2415
2416     /**
2417      * Returns whether or not current color is lighter than another color
2418      * @param color Color to compare against
2419      */
2420     Color.prototype.lighterThan = function (color) {
2421         if (!(color instanceof Color)) {
2422             color = new Color(color);
2423         }
2424
2425         return this.yuv.y > color.yuv.y;
2426     };
2427
2428     /**
2429      * Returns the result of mixing current color with another color
2430      * @param color Color to mix with
2431      * @param multiplier How much to mix with the other color
2432      */
2433      /*
2434     Color.prototype.mix = function (color, multiplier) {
2435         if (!(color instanceof Color)) {
2436             color = new Color(color);
2437         }
2438
2439         var r = this.rgb.r;
2440         var g = this.rgb.g;
2441         var b = this.rgb.b;
2442         var a = this.alpha;
2443
2444         var m = typeof multiplier !== 'undefined' ? multiplier : 0.5;
2445
2446         //todo: write a lerp function
2447         r = r + m * (color.rgb.r - r);
2448         g = g + m * (color.rgb.g - g);
2449         b = b + m * (color.rgb.b - b);
2450         a = a + m * (color.alpha - a);
2451
2452         return new Color(Color.rgbToHex(r, g, b), {
2453             'alpha': a
2454         });
2455     };
2456     */
2457
2458     /**
2459      * Returns the result of blending another color on top of current color with alpha
2460      * @param color Color to blend on top of current color, i.e. "Ca"
2461      */
2462      //todo: see if .blendAlpha can be merged into .mix
2463     Color.prototype.blendAlpha = function (color) {
2464         if (!(color instanceof Color)) {
2465             color = new Color(color);
2466         }
2467
2468         var Ca = color;
2469         var Cb = this;
2470
2471         //todo: write alpha blending function
2472         var r = Ca.alpha * Ca.rgb.r + (1 - Ca.alpha) * Cb.rgb.r;
2473         var g = Ca.alpha * Ca.rgb.g + (1 - Ca.alpha) * Cb.rgb.g;
2474         var b = Ca.alpha * Ca.rgb.b + (1 - Ca.alpha) * Cb.rgb.b;
2475
2476         return new Color(Color.rgbToHex(r, g, b));
2477     };
2478
2479     module.exports = Color;
2480
2481
2482 /***/ },
2483 /* 9 */
2484 /***/ function(module, exports, __webpack_require__) {
2485
2486     /**
2487      * toString ref.
2488      */
2489
2490     var toString = Object.prototype.toString;
2491
2492     /**
2493      * Return the type of `val`.
2494      *
2495      * @param {Mixed} val
2496      * @return {String}
2497      * @api public
2498      */
2499
2500     module.exports = function(val){
2501       switch (toString.call(val)) {
2502         case '[object Date]': return 'date';
2503         case '[object RegExp]': return 'regexp';
2504         case '[object Arguments]': return 'arguments';
2505         case '[object Array]': return 'array';
2506         case '[object Error]': return 'error';
2507       }
2508
2509       if (val === null) return 'null';
2510       if (val === undefined) return 'undefined';
2511       if (val !== val) return 'nan';
2512       if (val && val.nodeType === 1) return 'element';
2513
2514       val = val.valueOf
2515         ? val.valueOf()
2516         : Object.prototype.valueOf.apply(val)
2517
2518       return typeof val;
2519     };
2520
2521
2522 /***/ },
2523 /* 10 */
2524 /***/ function(module, exports, __webpack_require__) {
2525
2526     
2527     exports = module.exports = trim;
2528
2529     function trim(str){
2530       return str.replace(/^\s*|\s*$/g, '');
2531     }
2532
2533     exports.left = function(str){
2534       return str.replace(/^\s*/, '');
2535     };
2536
2537     exports.right = function(str){
2538       return str.replace(/\s*$/, '');
2539     };
2540
2541
2542 /***/ }
2543 /******/ ])
2544 });
2545 ;
2546 (function(ctx, isMeteorPackage) {
2547     if (isMeteorPackage) {
2548         Holder = ctx.Holder;
2549     }
2550 })(this, typeof Meteor !== 'undefined' && typeof Package !== 'undefined');