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