holder.js 88 KB

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