holder_rails 2.7.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 79563e575da1d9d7c264c59a17d32a8915167871
4
- data.tar.gz: a5ddf864af6375f97cc327af3ed3418d172fe567
3
+ metadata.gz: 3ce17582d2875c6a55f69eb0b88df95057f05b0e
4
+ data.tar.gz: 3f98323d52721e5e86f86abb1ed8c6c62012a40e
5
5
  SHA512:
6
- metadata.gz: 2df3996873ccde136425e8c6f662a53ccc604932867e2c55b9b5c0df97d4c9fad26b44967c538511d3b7605a16c25ffa90233e377c56e7b3787057222285c73b
7
- data.tar.gz: 715971f18e1c94b6702112e34c3e3cd7a79e9c906cb49d835f7e317a22d65fba497e4fc9f6c595e3987a70ede1a8f7a052bbff1c219196de272d952d5ee48a59
6
+ metadata.gz: bcb36fed3e0cbd0c46ff569e3d249f08d790da9fd143eddb0189da38f6e7aa48985245a444ff7dcff0cd4147e522ba13d38034fcb5832860372aec055d4e4c4b
7
+ data.tar.gz: ab5cca3f708a29f01abf1309ae402eac2eb12e9ea180af710acdb061a34c46abe9ac1731609b3dd63422404b6dce8742973816d22e2563a101a9a13daad5d3af
@@ -1,3 +1,3 @@
1
1
  module HolderRails
2
- VERSION = "2.7.1"
2
+ VERSION = "2.8.0"
3
3
  end
@@ -1,12 +1,12 @@
1
1
  /*!
2
2
 
3
3
  Holder - client side image placeholders
4
- Version 2.7.1+6hydf
4
+ Version 2.8.0+7srgw
5
5
  © 2015 Ivan Malopinsky - http://imsky.co
6
6
 
7
7
  Site: http://holderjs.com
8
8
  Issues: https://github.com/imsky/holder/issues
9
- License: http://opensource.org/licenses/MIT
9
+ License: MIT
10
10
 
11
11
  */
12
12
  (function (window) {
@@ -283,27 +283,42 @@ return /******/ (function(modules) { // webpackBootstrap
283
283
  /************************************************************************/
284
284
  /******/ ([
285
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 */
286
298
  /***/ function(module, exports, __webpack_require__) {
287
299
 
288
300
  /* WEBPACK VAR INJECTION */(function(global) {/*
289
301
  Holder.js - client side image placeholders
290
- © 2012-2015 Ivan Malopinsky - http://imsky.co
302
+ (c) 2012-2015 Ivan Malopinsky - http://imsky.co
291
303
  */
292
304
 
293
305
  //Libraries and functions
294
- var onDomReady = __webpack_require__(1);
295
- var SceneGraph = __webpack_require__(2);
296
- var utils = __webpack_require__(3);
297
- var querystring = __webpack_require__(4);
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);
298
314
 
299
315
  var extend = utils.extend;
300
- var getNodeArray = utils.getNodeArray;
301
316
  var dimensionCheck = utils.dimensionCheck;
302
317
 
303
318
  //Constants and definitions
304
319
  var SVG_NS = 'http://www.w3.org/2000/svg';
305
320
  var NODE_TYPE_COMMENT = 8;
306
- var version = '2.7.1';
321
+ var version = '2.8.0';
307
322
  var generatorComment = '\n' +
308
323
  'Created with Holder.js ' + version + '.\n' +
309
324
  'Learn more at http://holderjs.com\n' +
@@ -328,16 +343,17 @@ return /******/ (function(modules) { // webpackBootstrap
328
343
  * Appends a placeholder to an element
329
344
  *
330
345
  * @param {string} src Placeholder URL string
331
- * @param {string} el Selector of target element(s)
346
+ * @param el A selector or a reference to a DOM node
332
347
  */
333
348
  addImage: function(src, el) {
334
- var node = document.querySelectorAll(el);
349
+ //todo: use jquery fallback if available for all QSA references
350
+ var node = DOM.getNodeArray(el);
335
351
  if (node.length) {
336
352
  for (var i = 0, l = node.length; i < l; i++) {
337
- var img = newEl('img');
353
+ var img = DOM.newEl('img');
338
354
  var domProps = {};
339
- domProps[App.vars.dataAttr] = src;
340
- setAttr(img, domProps);
355
+ domProps[App.setup.dataAttr] = src;
356
+ DOM.setAttr(img, domProps);
341
357
  node[i].appendChild(img);
342
358
  }
343
359
  }
@@ -366,22 +382,24 @@ return /******/ (function(modules) { // webpackBootstrap
366
382
  * @param {Object} userOptions Options object, can contain domain, themes, images, and bgnodes properties
367
383
  */
368
384
  run: function(userOptions) {
385
+ //todo: split processing into separate queues
369
386
  userOptions = userOptions || {};
370
387
  var engineSettings = {};
371
388
  var options = extend(App.settings, userOptions);
372
389
 
373
390
  App.vars.preempted = true;
374
- App.vars.dataAttr = options.dataAttr || App.vars.dataAttr;
391
+ App.vars.dataAttr = options.dataAttr || App.setup.dataAttr;
392
+ App.vars.lineWrapRatio = options.lineWrapRatio || App.setup.lineWrapRatio;
375
393
 
376
394
  engineSettings.renderer = options.renderer ? options.renderer : App.setup.renderer;
377
395
  if (App.setup.renderers.join(',').indexOf(engineSettings.renderer) === -1) {
378
396
  engineSettings.renderer = App.setup.supportsSVG ? 'svg' : (App.setup.supportsCanvas ? 'canvas' : 'html');
379
397
  }
380
398
 
381
- var images = getNodeArray(options.images);
382
- var bgnodes = getNodeArray(options.bgnodes);
383
- var stylenodes = getNodeArray(options.stylenodes);
384
- var objects = getNodeArray(options.objects);
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);
385
403
 
386
404
  engineSettings.stylesheets = [];
387
405
  engineSettings.svgXMLStylesheet = true;
@@ -392,7 +410,7 @@ return /******/ (function(modules) { // webpackBootstrap
392
410
  if (styleNode.attributes.rel && styleNode.attributes.href && styleNode.attributes.rel.value == 'stylesheet') {
393
411
  var href = styleNode.attributes.href.value;
394
412
  //todo: write isomorphic relative-to-absolute URL function
395
- var proxyLink = newEl('a');
413
+ var proxyLink = DOM.newEl('a');
396
414
  proxyLink.href = href;
397
415
  var stylesheetURL = proxyLink.protocol + '//' + proxyLink.host + proxyLink.pathname + proxyLink.search;
398
416
  engineSettings.stylesheets.push(stylesheetURL);
@@ -404,13 +422,7 @@ return /******/ (function(modules) { // webpackBootstrap
404
422
  if (!global.getComputedStyle) continue;
405
423
  var backgroundImage = global.getComputedStyle(bgnodes[i], null).getPropertyValue('background-image');
406
424
  var dataBackgroundImage = bgnodes[i].getAttribute('data-background-src');
407
- var rawURL = null;
408
-
409
- if (dataBackgroundImage == null) {
410
- rawURL = backgroundImage;
411
- } else {
412
- rawURL = dataBackgroundImage;
413
- }
425
+ var rawURL = dataBackgroundImage || backgroundImage;
414
426
 
415
427
  var holderURL = null;
416
428
  var holderString = '?' + options.domain + '/';
@@ -507,7 +519,6 @@ return /******/ (function(modules) { // webpackBootstrap
507
519
  objects: 'object',
508
520
  bgnodes: 'body .holderjs',
509
521
  stylenodes: 'head link.holderjs',
510
- stylesheets: [],
511
522
  themes: {
512
523
  'gray': {
513
524
  background: '#EEEEEE',
@@ -539,69 +550,6 @@ return /******/ (function(modules) { // webpackBootstrap
539
550
  size: 10,
540
551
  units: 'pt',
541
552
  scale: 1 / 16
542
- },
543
- //todo: remove in 2.8
544
- flags: {
545
- dimensions: {
546
- regex: /^(\d+)x(\d+)$/,
547
- output: function(val) {
548
- var exec = this.regex.exec(val);
549
- return {
550
- width: +exec[1],
551
- height: +exec[2]
552
- };
553
- }
554
- },
555
- fluid: {
556
- regex: /^([0-9]+%?)x([0-9]+%?)$/,
557
- output: function(val) {
558
- var exec = this.regex.exec(val);
559
- return {
560
- width: exec[1],
561
- height: exec[2]
562
- };
563
- }
564
- },
565
- colors: {
566
- regex: /(?:#|\^)([0-9a-f]{3,})\:(?:#|\^)([0-9a-f]{3,})/i,
567
- output: function(val) {
568
- var exec = this.regex.exec(val);
569
- return {
570
- foreground: '#' + exec[2],
571
- background: '#' + exec[1]
572
- };
573
- }
574
- },
575
- text: {
576
- regex: /text\:(.*)/,
577
- output: function(val) {
578
- return this.regex.exec(val)[1].replace('\\/', '/');
579
- }
580
- },
581
- font: {
582
- regex: /font\:(.*)/,
583
- output: function(val) {
584
- return this.regex.exec(val)[1];
585
- }
586
- },
587
- auto: {
588
- regex: /^auto$/
589
- },
590
- textmode: {
591
- regex: /textmode\:(.*)/,
592
- output: function(val) {
593
- return this.regex.exec(val)[1];
594
- }
595
- },
596
- random: {
597
- regex: /^random$/
598
- },
599
- size: {
600
- regex: /size\:(\d+)/,
601
- output: function(val) {
602
- return this.regex.exec(val)[1];
603
- }
604
- }
605
553
  }
606
554
  };
607
555
 
@@ -640,11 +588,7 @@ return /******/ (function(modules) { // webpackBootstrap
640
588
  instanceOptions: options
641
589
  };
642
590
 
643
- if (url.match(/([\d]+p?)x([\d]+p?)(?:\?|$)/)) {
644
- return parseQueryString(url, holder);
645
- } else {
646
- return parseFlags(url, holder);
647
- }
591
+ return parseQueryString(url, holder);
648
592
  }
649
593
 
650
594
  /**
@@ -685,6 +629,11 @@ return /******/ (function(modules) { // webpackBootstrap
685
629
  holder.theme.foreground = (options.fg.indexOf('#') === -1 ? '#' : '') + options.fg;
686
630
  }
687
631
 
632
+ //todo: add automatic foreground to themes without foreground
633
+ if (options.bg && !options.fg) {
634
+ holder.autoFg = true;
635
+ }
636
+
688
637
  if (options.theme && holder.instanceOptions.themes.hasOwnProperty(options.theme)) {
689
638
  holder.theme = extend(holder.instanceOptions.themes[options.theme], null);
690
639
  }
@@ -717,6 +666,8 @@ return /******/ (function(modules) { // webpackBootstrap
717
666
 
718
667
  holder.auto = utils.truthy(options.auto);
719
668
 
669
+ holder.outline = utils.truthy(options.outline);
670
+
720
671
  if (utils.truthy(options.random)) {
721
672
  App.vars.cache.themeKeys = App.vars.cache.themeKeys || Object.keys(holder.instanceOptions.themes);
722
673
  var _theme = App.vars.cache.themeKeys[0 | Math.random() * App.vars.cache.themeKeys.length];
@@ -727,88 +678,6 @@ return /******/ (function(modules) { // webpackBootstrap
727
678
  return holder;
728
679
  }
729
680
 
730
- //todo: remove in 2.8
731
- /**
732
- * Processes a Holder URL and extracts flags
733
- *
734
- * @private
735
- * @deprecated
736
- * @param url URL
737
- * @param holder Staging Holder object
738
- */
739
- function parseFlags(url, holder) {
740
- var render = false;
741
- var vtab = String.fromCharCode(11);
742
- var flags = url.replace(/([^\\])\//g, '$1' + vtab).split(vtab);
743
- var uriRegex = /%[0-9a-f]{2}/gi;
744
- var options = holder.instanceOptions;
745
-
746
- holder.holderURL = [];
747
-
748
- for (var fl = flags.length, j = 0; j < fl; j++) {
749
- var flag = flags[j];
750
- if (flag.match(uriRegex)) {
751
- try {
752
- flag = decodeURIComponent(flag);
753
- } catch (e) {
754
- flag = flags[j];
755
- }
756
- }
757
-
758
- var push = false;
759
-
760
- if (App.flags.dimensions.match(flag)) {
761
- render = true;
762
- holder.dimensions = App.flags.dimensions.output(flag);
763
- push = true;
764
- } else if (App.flags.fluid.match(flag)) {
765
- render = true;
766
- holder.dimensions = App.flags.fluid.output(flag);
767
- holder.fluid = true;
768
- push = true;
769
- } else if (App.flags.textmode.match(flag)) {
770
- holder.textmode = App.flags.textmode.output(flag);
771
- push = true;
772
- } else if (App.flags.colors.match(flag)) {
773
- var colors = App.flags.colors.output(flag);
774
- holder.theme = extend(holder.theme, colors);
775
- push = true;
776
- } else if (options.themes[flag]) {
777
- //If a theme is specified, it will override custom colors
778
- if (options.themes.hasOwnProperty(flag)) {
779
- holder.theme = extend(options.themes[flag], null);
780
- }
781
- push = true;
782
- } else if (App.flags.font.match(flag)) {
783
- holder.font = App.flags.font.output(flag);
784
- push = true;
785
- } else if (App.flags.auto.match(flag)) {
786
- holder.auto = true;
787
- push = true;
788
- } else if (App.flags.text.match(flag)) {
789
- holder.text = App.flags.text.output(flag);
790
- push = true;
791
- } else if (App.flags.size.match(flag)) {
792
- holder.size = App.flags.size.output(flag);
793
- push = true;
794
- } else if (App.flags.random.match(flag)) {
795
- if (App.vars.cache.themeKeys == null) {
796
- App.vars.cache.themeKeys = Object.keys(options.themes);
797
- }
798
- var theme = App.vars.cache.themeKeys[0 | Math.random() * App.vars.cache.themeKeys.length];
799
- holder.theme = extend(options.themes[theme], null);
800
- push = true;
801
- }
802
-
803
- if (push) {
804
- holder.holderURL.push(flag);
805
- }
806
- }
807
- holder.holderURL.unshift(options.domain);
808
- holder.holderURL = holder.holderURL.join('/');
809
- return render ? holder : false;
810
- }
811
-
812
681
  /**
813
682
  * Modifies the DOM to fit placeholders and sets up resizable image callbacks (for fluid and automatically sized placeholders)
814
683
  *
@@ -858,14 +727,14 @@ return /******/ (function(modules) { // webpackBootstrap
858
727
 
859
728
  if (mode == 'background') {
860
729
  if (el.getAttribute('data-background-src') == null) {
861
- setAttr(el, {
730
+ DOM.setAttr(el, {
862
731
  'data-background-src': holderURL
863
732
  });
864
733
  }
865
734
  } else {
866
735
  var domProps = {};
867
736
  domProps[App.vars.dataAttr] = holderURL;
868
- setAttr(el, domProps);
737
+ DOM.setAttr(el, domProps);
869
738
  }
870
739
 
871
740
  flags.theme = theme;
@@ -877,7 +746,7 @@ return /******/ (function(modules) { // webpackBootstrap
877
746
  };
878
747
 
879
748
  if (mode == 'image' || mode == 'fluid') {
880
- setAttr(el, {
749
+ DOM.setAttr(el, {
881
750
  'alt': (theme.text ? theme.text + ' [' + dimensionsCaption + ']' : dimensionsCaption)
882
751
  });
883
752
  }
@@ -948,8 +817,8 @@ return /******/ (function(modules) { // webpackBootstrap
948
817
  function render(renderSettings) {
949
818
  var image = null;
950
819
  var mode = renderSettings.mode;
951
- var holderSettings = renderSettings.holderSettings;
952
820
  var el = renderSettings.el;
821
+ var holderSettings = renderSettings.holderSettings;
953
822
  var engineSettings = renderSettings.engineSettings;
954
823
 
955
824
  switch (engineSettings.renderer) {
@@ -1001,14 +870,14 @@ return /******/ (function(modules) { // webpackBootstrap
1001
870
  el.style.backgroundSize = scene.width + 'px ' + scene.height + 'px';
1002
871
  } else {
1003
872
  if (el.nodeName.toLowerCase() === 'img') {
1004
- setAttr(el, {
873
+ DOM.setAttr(el, {
1005
874
  'src': image
1006
875
  });
1007
876
  } else if (el.nodeName.toLowerCase() === 'object') {
1008
- setAttr(el, {
877
+ DOM.setAttr(el, {
1009
878
  'data': image
1010
879
  });
1011
- setAttr(el, {
880
+ DOM.setAttr(el, {
1012
881
  'type': 'image/svg+xml'
1013
882
  });
1014
883
  }
@@ -1020,22 +889,22 @@ return /******/ (function(modules) { // webpackBootstrap
1020
889
  }
1021
890
  //todo: refactor this code into a function
1022
891
  if (el.nodeName.toLowerCase() === 'img') {
1023
- setAttr(el, {
892
+ DOM.setAttr(el, {
1024
893
  'src': image
1025
894
  });
1026
895
  } else if (el.nodeName.toLowerCase() === 'object') {
1027
- setAttr(el, {
896
+ DOM.setAttr(el, {
1028
897
  'data': image
1029
898
  });
1030
- setAttr(el, {
899
+ DOM.setAttr(el, {
1031
900
  'type': 'image/svg+xml'
1032
901
  });
1033
902
  }
1034
- }, 100);
903
+ }, 150);
1035
904
  }
1036
905
  }
1037
906
  //todo: account for re-rendering
1038
- setAttr(el, {
907
+ DOM.setAttr(el, {
1039
908
  'data-holder-rendered': true
1040
909
  });
1041
910
  }
@@ -1093,11 +962,33 @@ return /******/ (function(modules) { // webpackBootstrap
1093
962
  holderBg.resize(scene.width, scene.height);
1094
963
  sceneGraph.root.add(holderBg);
1095
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
+
1096
987
  var holderTextGroup = new Shape.Group('holderTextGroup', {
1097
988
  text: scene.text,
1098
989
  align: scene.align,
1099
990
  font: scene.font,
1100
- fill: scene.theme.foreground
991
+ fill: holderTextColor
1101
992
  });
1102
993
 
1103
994
  holderTextGroup.moveTo(null, null, 1);
@@ -1119,7 +1010,7 @@ return /******/ (function(modules) { // webpackBootstrap
1119
1010
  parent.height += line.height;
1120
1011
  }
1121
1012
 
1122
- var sceneMargin = scene.width * App.setup.lineWrapRatio;
1013
+ var sceneMargin = scene.width * App.vars.lineWrapRatio;
1123
1014
  var maxLineWidth = sceneMargin;
1124
1015
 
1125
1016
  if (tpdata.lineCount > 1) {
@@ -1131,7 +1022,7 @@ return /******/ (function(modules) { // webpackBootstrap
1131
1022
 
1132
1023
  //Double margin so that left/right-aligned next is not flush with edge of image
1133
1024
  if (scene.align === 'left' || scene.align === 'right') {
1134
- maxLineWidth = scene.width * (1 - (1 - (App.setup.lineWrapRatio)) * 2);
1025
+ maxLineWidth = scene.width * (1 - (1 - (App.vars.lineWrapRatio)) * 2);
1135
1026
  }
1136
1027
 
1137
1028
  for (var i = 0; i < tpdata.words.length; i++) {
@@ -1200,7 +1091,6 @@ return /******/ (function(modules) { // webpackBootstrap
1200
1091
  }
1201
1092
 
1202
1093
  //todo: renderlist
1203
-
1204
1094
  return sceneGraph;
1205
1095
  }
1206
1096
 
@@ -1384,14 +1274,14 @@ return /******/ (function(modules) { // webpackBootstrap
1384
1274
  firstTimeSetup = true;
1385
1275
  }
1386
1276
 
1387
- svg = initSVG(svg, rootNode.properties.width, rootNode.properties.height);
1277
+ svg = SVG.initSVG(svg, rootNode.properties.width, rootNode.properties.height);
1388
1278
  //Show staging element before staging
1389
1279
  svg.style.display = 'block';
1390
1280
 
1391
1281
  if (firstTimeSetup) {
1392
- stagingText = newEl('text', SVG_NS);
1282
+ stagingText = DOM.newEl('text', SVG_NS);
1393
1283
  stagingTextNode = tnode(null);
1394
- setAttr(stagingText, {
1284
+ DOM.setAttr(stagingText, {
1395
1285
  x: 0
1396
1286
  });
1397
1287
  stagingText.appendChild(stagingTextNode);
@@ -1408,7 +1298,7 @@ return /******/ (function(modules) { // webpackBootstrap
1408
1298
 
1409
1299
  var holderTextGroup = rootNode.children.holderTextGroup;
1410
1300
  var htgProps = holderTextGroup.properties;
1411
- setAttr(stagingText, {
1301
+ DOM.setAttr(stagingText, {
1412
1302
  'y': htgProps.font.size,
1413
1303
  'style': utils.cssProps({
1414
1304
  'font-weight': htgProps.font.weight,
@@ -1422,7 +1312,7 @@ return /******/ (function(modules) { // webpackBootstrap
1422
1312
  var stagingTextBBox = stagingText.getBBox();
1423
1313
 
1424
1314
  //Get line count and split the string into words
1425
- var lineCount = Math.ceil(stagingTextBBox.width / (rootNode.properties.width * App.setup.lineWrapRatio));
1315
+ var lineCount = Math.ceil(stagingTextBBox.width / (rootNode.properties.width * App.vars.lineWrapRatio));
1426
1316
  var words = htgProps.text.split(' ');
1427
1317
  var newlines = htgProps.text.match(/\\n/g);
1428
1318
  lineCount += newlines == null ? 0 : newlines.length;
@@ -1467,7 +1357,7 @@ return /******/ (function(modules) { // webpackBootstrap
1467
1357
  })();
1468
1358
 
1469
1359
  var sgCanvasRenderer = (function() {
1470
- var canvas = newEl('canvas');
1360
+ var canvas = DOM.newEl('canvas');
1471
1361
  var ctx = null;
1472
1362
 
1473
1363
  return function(sceneGraph) {
@@ -1479,8 +1369,33 @@ return /******/ (function(modules) { // webpackBootstrap
1479
1369
  canvas.height = App.dpr(root.properties.height);
1480
1370
  ctx.textBaseline = 'middle';
1481
1371
 
1482
- ctx.fillStyle = root.children.holderBg.properties.fill;
1483
- ctx.fillRect(0, 0, App.dpr(root.children.holderBg.width), App.dpr(root.children.holderBg.height));
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
+ }
1484
1399
 
1485
1400
  var textGroup = root.children.holderTextGroup;
1486
1401
  var tgProps = textGroup.properties;
@@ -1505,9 +1420,9 @@ return /******/ (function(modules) { // webpackBootstrap
1505
1420
  var sgSVGRenderer = (function() {
1506
1421
  //Prevent IE <9 from initializing SVG renderer
1507
1422
  if (!global.XMLSerializer) return;
1508
- var xml = createXML();
1509
- var svg = initSVG(null, 0, 0);
1510
- var bgEl = newEl('rect', SVG_NS);
1423
+ var xml = DOM.createXML();
1424
+ var svg = SVG.initSVG(null, 0, 0);
1425
+ var bgEl = DOM.newEl('rect', SVG_NS);
1511
1426
  svg.appendChild(bgEl);
1512
1427
 
1513
1428
  //todo: create a reusable pool for textNodes, resize if more words present
@@ -1515,7 +1430,7 @@ return /******/ (function(modules) { // webpackBootstrap
1515
1430
  return function(sceneGraph, renderSettings) {
1516
1431
  var root = sceneGraph.root;
1517
1432
 
1518
- initSVG(svg, root.properties.width, root.properties.height);
1433
+ SVG.initSVG(svg, root.properties.width, root.properties.height);
1519
1434
 
1520
1435
  var groups = svg.querySelectorAll('g');
1521
1436
 
@@ -1525,10 +1440,10 @@ return /******/ (function(modules) { // webpackBootstrap
1525
1440
 
1526
1441
  var holderURL = renderSettings.holderSettings.flags.holderURL;
1527
1442
  var holderId = 'holder_' + (Number(new Date()) + 32768 + (0 | Math.random() * 32768)).toString(16);
1528
- var sceneGroupEl = newEl('g', SVG_NS);
1443
+ var sceneGroupEl = DOM.newEl('g', SVG_NS);
1529
1444
  var textGroup = root.children.holderTextGroup;
1530
1445
  var tgProps = textGroup.properties;
1531
- var textGroupEl = newEl('g', SVG_NS);
1446
+ var textGroupEl = DOM.newEl('g', SVG_NS);
1532
1447
  var tpdata = textGroup.textPositionData;
1533
1448
  var textCSSRule = '#' + holderId + ' text { ' +
1534
1449
  utils.cssProps({
@@ -1540,8 +1455,9 @@ return /******/ (function(modules) { // webpackBootstrap
1540
1455
  var commentNode = xml.createComment('\n' + 'Source URL: ' + holderURL + generatorComment);
1541
1456
  var holderCSS = xml.createCDATASection(textCSSRule);
1542
1457
  var styleEl = svg.querySelector('style');
1458
+ var bg = root.children.holderBg;
1543
1459
 
1544
- setAttr(sceneGroupEl, {
1460
+ DOM.setAttr(sceneGroupEl, {
1545
1461
  id: holderId
1546
1462
  });
1547
1463
 
@@ -1549,13 +1465,38 @@ return /******/ (function(modules) { // webpackBootstrap
1549
1465
  styleEl.appendChild(holderCSS);
1550
1466
 
1551
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
+
1552
1493
  sceneGroupEl.appendChild(textGroupEl);
1553
1494
  svg.appendChild(sceneGroupEl);
1554
1495
 
1555
- setAttr(bgEl, {
1556
- 'width': root.children.holderBg.width,
1557
- 'height': root.children.holderBg.height,
1558
- 'fill': root.children.holderBg.properties.fill
1496
+ DOM.setAttr(bgEl, {
1497
+ 'width': bg.width,
1498
+ 'height': bg.height,
1499
+ 'fill': bg.properties.fill
1559
1500
  });
1560
1501
 
1561
1502
  textGroup.y += tpdata.boundingBox.height * 0.8;
@@ -1567,10 +1508,10 @@ return /******/ (function(modules) { // webpackBootstrap
1567
1508
  var x = textGroup.x + line.x + word.x;
1568
1509
  var y = textGroup.y + line.y + word.y;
1569
1510
 
1570
- var textEl = newEl('text', SVG_NS);
1511
+ var textEl = DOM.newEl('text', SVG_NS);
1571
1512
  var textNode = document.createTextNode(null);
1572
1513
 
1573
- setAttr(textEl, {
1514
+ DOM.setAttr(textEl, {
1574
1515
  'x': x,
1575
1516
  'y': y
1576
1517
  });
@@ -1582,152 +1523,13 @@ return /******/ (function(modules) { // webpackBootstrap
1582
1523
  }
1583
1524
 
1584
1525
  //todo: factor the background check up the chain, perhaps only return reference
1585
- var svgString = svgStringToDataURI(serializeSVG(svg, renderSettings.engineSettings), renderSettings.mode === 'background');
1526
+ var svgString = SVG.svgStringToDataURI(SVG.serializeSVG(svg, renderSettings.engineSettings), renderSettings.mode === 'background');
1586
1527
  return svgString;
1587
1528
  };
1588
1529
  })();
1589
1530
 
1590
1531
  //Helpers
1591
1532
 
1592
- //todo: move svg-related helpers to a dedicated file
1593
-
1594
- /**
1595
- * Converts serialized SVG to a string suitable for data URI use
1596
- * @param svgString Serialized SVG string
1597
- * @param [base64] Use base64 encoding for data URI
1598
- */
1599
- var svgStringToDataURI = function() {
1600
- var rawPrefix = 'data:image/svg+xml;charset=UTF-8,';
1601
- var base64Prefix = 'data:image/svg+xml;charset=UTF-8;base64,';
1602
-
1603
- return function(svgString, base64) {
1604
- if (base64) {
1605
- return base64Prefix + btoa(unescape(encodeURIComponent(svgString)));
1606
- } else {
1607
- return rawPrefix + encodeURIComponent(svgString);
1608
- }
1609
- };
1610
- }();
1611
-
1612
- /**
1613
- * Generic new DOM element function
1614
- *
1615
- * @private
1616
- * @param tag Tag to create
1617
- * @param namespace Optional namespace value
1618
- */
1619
- function newEl(tag, namespace) {
1620
- if (namespace == null) {
1621
- return document.createElement(tag);
1622
- } else {
1623
- return document.createElementNS(namespace, tag);
1624
- }
1625
- }
1626
-
1627
- /**
1628
- * Generic setAttribute function
1629
- *
1630
- * @private
1631
- * @param el Reference to DOM element
1632
- * @param attrs Object with attribute keys and values
1633
- */
1634
- function setAttr(el, attrs) {
1635
- for (var a in attrs) {
1636
- el.setAttribute(a, attrs[a]);
1637
- }
1638
- }
1639
-
1640
- /**
1641
- * Generic SVG element creation function
1642
- *
1643
- * @private
1644
- * @param svg SVG context, set to null if new
1645
- * @param width Document width
1646
- * @param height Document height
1647
- */
1648
- function initSVG(svg, width, height) {
1649
- var defs, style;
1650
-
1651
- if (svg == null) {
1652
- svg = newEl('svg', SVG_NS);
1653
- defs = newEl('defs', SVG_NS);
1654
- style = newEl('style', SVG_NS);
1655
- setAttr(style, {
1656
- 'type': 'text/css'
1657
- });
1658
- defs.appendChild(style);
1659
- svg.appendChild(defs);
1660
- } else {
1661
- style = svg.querySelector('style');
1662
- }
1663
-
1664
- //IE throws an exception if this is set and Chrome requires it to be set
1665
- if (svg.webkitMatchesSelector) {
1666
- svg.setAttribute('xmlns', SVG_NS);
1667
- }
1668
-
1669
- //Remove comment nodes
1670
- for (var i = 0; i < svg.childNodes.length; i++) {
1671
- if (svg.childNodes[i].nodeType === NODE_TYPE_COMMENT) {
1672
- svg.removeChild(svg.childNodes[i]);
1673
- }
1674
- }
1675
-
1676
- //Remove CSS
1677
- while (style.childNodes.length) {
1678
- style.removeChild(style.childNodes[0]);
1679
- }
1680
-
1681
- setAttr(svg, {
1682
- 'width': width,
1683
- 'height': height,
1684
- 'viewBox': '0 0 ' + width + ' ' + height,
1685
- 'preserveAspectRatio': 'none'
1686
- });
1687
-
1688
- return svg;
1689
- }
1690
-
1691
- /**
1692
- * Returns serialized SVG with XML processing instructions
1693
- *
1694
- * @private
1695
- * @param svg SVG context
1696
- * @param stylesheets CSS stylesheets to include
1697
- */
1698
- function serializeSVG(svg, engineSettings) {
1699
- if (!global.XMLSerializer) return;
1700
- var serializer = new XMLSerializer();
1701
- var svgCSS = '';
1702
- var stylesheets = engineSettings.stylesheets;
1703
-
1704
- //External stylesheets: Processing Instruction method
1705
- if (engineSettings.svgXMLStylesheet) {
1706
- var xml = createXML();
1707
- //Add <?xml-stylesheet ?> directives
1708
- for (var i = stylesheets.length - 1; i >= 0; i--) {
1709
- var csspi = xml.createProcessingInstruction('xml-stylesheet', 'href="' + stylesheets[i] + '" rel="stylesheet"');
1710
- xml.insertBefore(csspi, xml.firstChild);
1711
- }
1712
-
1713
- xml.removeChild(xml.documentElement);
1714
- svgCSS = serializer.serializeToString(xml);
1715
- }
1716
-
1717
- var svgText = serializer.serializeToString(svg);
1718
- svgText = svgText.replace(/\&amp;(\#[0-9]{2,}\;)/g, '&$1');
1719
- return svgCSS + svgText;
1720
- }
1721
-
1722
- /**
1723
- * Creates a XML document
1724
- * @private
1725
- */
1726
- function createXML() {
1727
- if (!global.DOMParser) return;
1728
- return new DOMParser().parseFromString('<xml />', 'application/xml');
1729
- }
1730
-
1731
1533
  /**
1732
1534
  * Prevents a function from being called too often, waits until a timer elapses to call it again
1733
1535
  *
@@ -1769,6 +1571,7 @@ return /******/ (function(modules) { // webpackBootstrap
1769
1571
  supportsCanvas: false,
1770
1572
  supportsSVG: false,
1771
1573
  lineWrapRatio: 0.9,
1574
+ dataAttr: 'data-src',
1772
1575
  renderers: ['html', 'canvas', 'svg']
1773
1576
  };
1774
1577
 
@@ -1785,8 +1588,7 @@ return /******/ (function(modules) { // webpackBootstrap
1785
1588
  invisibleId: 0,
1786
1589
  visibilityCheckStarted: false,
1787
1590
  debounceTimer: null,
1788
- cache: {},
1789
- dataAttr: 'data-src'
1591
+ cache: {}
1790
1592
  };
1791
1593
 
1792
1594
  //Pre-flight
@@ -1795,7 +1597,7 @@ return /******/ (function(modules) { // webpackBootstrap
1795
1597
  var devicePixelRatio = 1,
1796
1598
  backingStoreRatio = 1;
1797
1599
 
1798
- var canvas = newEl('canvas');
1600
+ var canvas = DOM.newEl('canvas');
1799
1601
  var ctx = null;
1800
1602
 
1801
1603
  if (canvas.getContext) {
@@ -1847,34 +1649,142 @@ return /******/ (function(modules) { // webpackBootstrap
1847
1649
  /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
1848
1650
 
1849
1651
  /***/ },
1850
- /* 1 */
1652
+ /* 2 */
1851
1653
  /***/ function(module, exports, __webpack_require__) {
1852
1654
 
1853
- /*!
1854
- * onDomReady.js 1.4.0 (c) 2013 Tubal Martin - MIT license
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`.
1855
1673
  *
1856
- * Specially modified to work with Holder.js
1674
+ * @param {String} str
1675
+ * @return {Object}
1676
+ * @api public
1857
1677
  */
1858
1678
 
1859
- function _onDomReady(win) {
1860
- //Lazy loading fix for Firefox < 3.6
1861
- //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
1862
- if (document.readyState == null && document.addEventListener) {
1863
- document.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
1864
- document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
1865
- document.readyState = "complete";
1866
- }, false);
1867
- document.readyState = "loading";
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;
1868
1697
  }
1869
-
1870
- var doc = win.document,
1871
- docElem = doc.documentElement,
1872
-
1873
- LOAD = "load",
1874
- FALSE = false,
1875
- ONLOAD = "on"+LOAD,
1876
- COMPLETE = "complete",
1877
- READYSTATE = "readyState",
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",
1878
1788
  ATTACHEVENT = "attachEvent",
1879
1789
  DETACHEVENT = "detachEvent",
1880
1790
  ADDEVENTLISTENER = "addEventListener",
@@ -2007,11 +1917,9 @@ return /******/ (function(modules) { // webpackBootstrap
2007
1917
  module.exports = typeof window !== "undefined" && _onDomReady(window);
2008
1918
 
2009
1919
  /***/ },
2010
- /* 2 */
1920
+ /* 4 */
2011
1921
  /***/ function(module, exports, __webpack_require__) {
2012
1922
 
2013
- var augment = __webpack_require__(5);
2014
-
2015
1923
  var SceneGraph = function(sceneProperties) {
2016
1924
  var nodeCount = 1;
2017
1925
 
@@ -2023,90 +1931,96 @@ return /******/ (function(modules) { // webpackBootstrap
2023
1931
  return parent;
2024
1932
  }
2025
1933
 
2026
- var SceneNode = augment.defclass({
2027
- constructor: function(name) {
2028
- nodeCount++;
2029
- this.parent = null;
2030
- this.children = {};
2031
- this.id = nodeCount;
2032
- this.name = 'n' + nodeCount;
2033
- if (name != null) {
2034
- this.name = name;
2035
- }
2036
- this.x = 0;
2037
- this.y = 0;
2038
- this.z = 0;
2039
- this.width = 0;
2040
- this.height = 0;
2041
- },
2042
- resize: function(width, height) {
2043
- if (width != null) {
2044
- this.width = width;
2045
- }
2046
- if (height != null) {
2047
- this.height = height;
2048
- }
2049
- },
2050
- moveTo: function(x, y, z) {
2051
- this.x = x != null ? x : this.x;
2052
- this.y = y != null ? y : this.y;
2053
- this.z = z != null ? z : this.z;
2054
- },
2055
- add: function(child) {
2056
- var name = child.name;
2057
- if (this.children[name] == null) {
2058
- this.children[name] = child;
2059
- child.parent = this;
2060
- } else {
2061
- throw 'SceneGraph: child with that name already exists: ' + name;
2062
- }
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;
2063
1950
  }
2064
- });
1951
+ if (height != null) {
1952
+ this.height = height;
1953
+ }
1954
+ };
2065
1955
 
2066
- var RootNode = augment(SceneNode, function(uber) {
2067
- this.constructor = function() {
2068
- uber.constructor.call(this, 'root');
2069
- this.properties = sceneProperties;
2070
- };
2071
- });
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
+ };
2072
1961
 
2073
- var Shape = augment(SceneNode, function(uber) {
2074
- function constructor(name, props) {
2075
- uber.constructor.call(this, name);
2076
- this.properties = {
2077
- fill: '#000'
2078
- };
2079
- if (props != null) {
2080
- merge(this.properties, props);
2081
- } else if (name != null && typeof name !== 'string') {
2082
- throw 'SceneGraph: invalid node name';
2083
- }
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;
2084
1969
  }
1970
+ };
2085
1971
 
2086
- this.Group = augment.extend(this, {
2087
- constructor: constructor,
2088
- type: 'group'
2089
- });
1972
+ var RootNode = function() {
1973
+ SceneNode.call(this, 'root');
1974
+ this.properties = sceneProperties;
1975
+ };
2090
1976
 
2091
- this.Rect = augment.extend(this, {
2092
- constructor: constructor,
2093
- type: 'rect'
2094
- });
1977
+ RootNode.prototype = new SceneNode();
2095
1978
 
2096
- this.Text = augment.extend(this, {
2097
- constructor: function(text) {
2098
- constructor.call(this);
2099
- this.properties.text = text;
2100
- },
2101
- type: 'text'
2102
- });
2103
- });
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();
2104
2014
 
2105
2015
  var root = new RootNode();
2106
2016
 
2107
- this.Shape = Shape;
2108
- this.root = root;
2017
+ this.Shape = {
2018
+ 'Rect': Rect,
2019
+ 'Text': Text,
2020
+ 'Group': Group
2021
+ };
2109
2022
 
2023
+ this.root = root;
2110
2024
  return this;
2111
2025
  };
2112
2026
 
@@ -2114,10 +2028,10 @@ return /******/ (function(modules) { // webpackBootstrap
2114
2028
 
2115
2029
 
2116
2030
  /***/ },
2117
- /* 3 */
2031
+ /* 5 */
2118
2032
  /***/ function(module, exports, __webpack_require__) {
2119
2033
 
2120
- /* WEBPACK VAR INJECTION */(function(global) {/**
2034
+ /**
2121
2035
  * Shallow object clone and merge
2122
2036
  *
2123
2037
  * @param a Object A
@@ -2175,30 +2089,6 @@ return /******/ (function(modules) { // webpackBootstrap
2175
2089
  return buf.join('');
2176
2090
  };
2177
2091
 
2178
-
2179
- /**
2180
- * Converts a value into an array of DOM nodes
2181
- *
2182
- * @param val A string, a NodeList, a Node, or an HTMLCollection
2183
- */
2184
- exports.getNodeArray = function(val) {
2185
- var retval = null;
2186
- if (typeof(val) == 'string') {
2187
- retval = document.querySelectorAll(val);
2188
- } else if (global.NodeList && val instanceof global.NodeList) {
2189
- retval = val;
2190
- } else if (global.Node && val instanceof global.Node) {
2191
- retval = [val];
2192
- } else if (global.HTMLCollection && val instanceof global.HTMLCollection) {
2193
- retval = val;
2194
- } else if (val instanceof Array) {
2195
- retval = val;
2196
- } else if (val === null) {
2197
- retval = [];
2198
- }
2199
- return retval;
2200
- };
2201
-
2202
2092
  /**
2203
2093
  * Checks if an image exists
2204
2094
  *
@@ -2258,170 +2148,339 @@ return /******/ (function(modules) { // webpackBootstrap
2258
2148
  return !!val;
2259
2149
  };
2260
2150
 
2261
- /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2262
2151
 
2263
2152
  /***/ },
2264
- /* 4 */
2153
+ /* 6 */
2265
2154
  /***/ function(module, exports, __webpack_require__) {
2266
2155
 
2267
- //Modified version of component/querystring
2268
- //Changes: updated dependencies, dot notation parsing, JSHint fixes
2269
- //Fork at https://github.com/imsky/querystring
2270
-
2271
- /**
2272
- * Module dependencies.
2273
- */
2274
-
2275
- var encode = encodeURIComponent;
2276
- var decode = decodeURIComponent;
2277
- var trim = __webpack_require__(6);
2278
- var type = __webpack_require__(7);
2156
+ /* WEBPACK VAR INJECTION */(function(global) {var DOM = __webpack_require__(7);
2279
2157
 
2280
- var arrayRegex = /(\w+)\[(\d+)\]/;
2281
- var objectRegex = /\w+\.\w+/;
2158
+ var SVG_NS = 'http://www.w3.org/2000/svg';
2159
+ var NODE_TYPE_COMMENT = 8;
2282
2160
 
2283
2161
  /**
2284
- * Parse the given query `str`.
2162
+ * Generic SVG element creation function
2285
2163
  *
2286
- * @param {String} str
2287
- * @return {Object}
2288
- * @api public
2164
+ * @private
2165
+ * @param svg SVG context, set to null if new
2166
+ * @param width Document width
2167
+ * @param height Document height
2289
2168
  */
2169
+ exports.initSVG = function(svg, width, height) {
2170
+ var defs, style, initialize = false;
2290
2171
 
2291
- exports.parse = function(str){
2292
- if ('string' !== typeof str) return {};
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
+ }
2293
2181
 
2294
- str = trim(str);
2295
- if ('' === str) return {};
2296
- if ('?' === str.charAt(0)) str = str.slice(1);
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
+ }
2297
2191
 
2298
- var obj = {};
2299
- var pairs = str.split('&');
2300
- for (var i = 0; i < pairs.length; i++) {
2301
- var parts = pairs[i].split('=');
2302
- var key = decode(parts[0]);
2303
- var m, ctx, prop;
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
+ }
2304
2196
 
2305
- if (m = arrayRegex.exec(key)) {
2306
- obj[m[1]] = obj[m[1]] || [];
2307
- obj[m[1]][m[2]] = decode(parts[1]);
2308
- continue;
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
+ }
2309
2202
  }
2310
2203
 
2311
- if (m = objectRegex.test(key)) {
2312
- m = key.split('.');
2313
- ctx = obj;
2314
-
2315
- while (m.length) {
2316
- prop = m.shift();
2204
+ //Remove CSS
2205
+ while (style.childNodes.length) {
2206
+ style.removeChild(style.childNodes[0]);
2207
+ }
2317
2208
 
2318
- if (!prop.length) continue;
2209
+ DOM.setAttr(svg, {
2210
+ 'width': width,
2211
+ 'height': height,
2212
+ 'viewBox': '0 0 ' + width + ' ' + height,
2213
+ 'preserveAspectRatio': 'none'
2214
+ });
2319
2215
 
2320
- if (!ctx[prop]) {
2321
- ctx[prop] = {};
2322
- } else if (ctx[prop] && typeof ctx[prop] !== 'object') {
2323
- break;
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);
2324
2233
  }
2234
+ };
2235
+ }();
2325
2236
 
2326
- if (!m.length) {
2327
- ctx[prop] = decode(parts[1]);
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);
2328
2257
  }
2329
2258
 
2330
- ctx = ctx[prop];
2331
- }
2259
+ xml.removeChild(xml.documentElement);
2260
+ svgCSS = serializer.serializeToString(xml);
2261
+ }
2332
2262
 
2333
- continue;
2263
+ var svgText = serializer.serializeToString(svg);
2264
+ svgText = svgText.replace(/\&amp;(\#[0-9]{2,}\;)/g, '&$1');
2265
+ return svgCSS + svgText;
2266
+ };
2267
+
2268
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2269
+
2270
+ /***/ },
2271
+ /* 7 */
2272
+ /***/ function(module, exports, __webpack_require__) {
2273
+
2274
+ /* WEBPACK VAR INJECTION */(function(global) {/**
2275
+ * Generic new DOM element function
2276
+ *
2277
+ * @private
2278
+ * @param tag Tag to create
2279
+ * @param namespace Optional namespace value
2280
+ */
2281
+ exports.newEl = function(tag, namespace) {
2282
+ if (!global.document) return;
2283
+
2284
+ if (namespace == null) {
2285
+ return document.createElement(tag);
2286
+ } else {
2287
+ return document.createElementNS(namespace, tag);
2334
2288
  }
2289
+ };
2335
2290
 
2336
- obj[parts[0]] = null == parts[1] ? '' : decode(parts[1]);
2337
- }
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
+ };
2338
2303
 
2339
- return obj;
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');
2340
2311
  };
2341
2312
 
2342
2313
  /**
2343
- * Stringify the given `obj`.
2314
+ * Converts a value into an array of DOM nodes
2344
2315
  *
2345
- * @param {Object} obj
2346
- * @return {String}
2347
- * @api public
2316
+ * @param val A string, a NodeList, a Node, or an HTMLCollection
2348
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
+ };
2349
2335
 
2350
- exports.stringify = function(obj){
2351
- if (!obj) return '';
2352
- var pairs = [];
2336
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2353
2337
 
2354
- for (var key in obj) {
2355
- var value = obj[key];
2338
+ /***/ },
2339
+ /* 8 */
2340
+ /***/ function(module, exports, __webpack_require__) {
2356
2341
 
2357
- if ('array' == type(value)) {
2358
- for (var i = 0; i < value.length; ++i) {
2359
- pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i]));
2360
- }
2361
- continue;
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);
2362
2349
  }
2363
2350
 
2364
- pairs.push(encode(key) + '=' + encode(obj[key]));
2365
- }
2351
+ if (/[^a-f0-9]+/i.test(color)) return;
2366
2352
 
2367
- return pairs.join('&');
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));
2368
2366
  };
2369
2367
 
2368
+ Color.rgbToHex = function (r, g, b) {
2369
+ return (((r | 0) << 16) + ((g | 0) << 8) + (b | 0)).toString(16);
2370
+ };
2370
2371
 
2371
- /***/ },
2372
- /* 5 */
2373
- /***/ function(module, exports, __webpack_require__) {
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
+ }
2374
2393
 
2375
- var Factory = function () {};
2376
- var slice = Array.prototype.slice;
2377
-
2378
- var augment = function (base, body) {
2379
- var uber = Factory.prototype = typeof base === "function" ? base.prototype : base;
2380
- var prototype = new Factory(), properties = body.apply(prototype, slice.call(arguments, 2).concat(uber));
2381
- if (typeof properties === "object") for (var key in properties) prototype[key] = properties[key];
2382
- if (!prototype.hasOwnProperty("constructor")) return prototype;
2383
- var constructor = prototype.constructor;
2384
- constructor.prototype = prototype;
2385
- return constructor;
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));
2386
2406
  };
2387
2407
 
2388
- augment.defclass = function (prototype) {
2389
- var constructor = prototype.constructor;
2390
- constructor.prototype = prototype;
2391
- return constructor;
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);
2392
2414
  };
2393
2415
 
2394
- augment.extend = function (base, body) {
2395
- return augment(base, function (uber) {
2396
- this.uber = uber;
2397
- return body;
2398
- });
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;
2399
2426
  };
2400
2427
 
2401
- module.exports = augment;
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
+ }
2402
2438
 
2403
- /***/ },
2404
- /* 6 */
2405
- /***/ function(module, exports, __webpack_require__) {
2439
+ var r = this.rgb.r;
2440
+ var g = this.rgb.g;
2441
+ var b = this.rgb.b;
2442
+ var a = this.alpha;
2406
2443
 
2407
-
2408
- exports = module.exports = trim;
2444
+ var m = typeof multiplier !== 'undefined' ? multiplier : 0.5;
2409
2445
 
2410
- function trim(str){
2411
- return str.replace(/^\s*|\s*$/g, '');
2412
- }
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);
2413
2451
 
2414
- exports.left = function(str){
2415
- return str.replace(/^\s*/, '');
2452
+ return new Color(Color.rgbToHex(r, g, b), {
2453
+ 'alpha': a
2454
+ });
2416
2455
  };
2456
+ */
2417
2457
 
2418
- exports.right = function(str){
2419
- return str.replace(/\s*$/, '');
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));
2420
2477
  };
2421
2478
 
2479
+ module.exports = Color;
2480
+
2422
2481
 
2423
2482
  /***/ },
2424
- /* 7 */
2483
+ /* 9 */
2425
2484
  /***/ function(module, exports, __webpack_require__) {
2426
2485
 
2427
2486
  /**
@@ -2460,6 +2519,26 @@ return /******/ (function(modules) { // webpackBootstrap
2460
2519
  };
2461
2520
 
2462
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
+
2463
2542
  /***/ }
2464
2543
  /******/ ])
2465
2544
  });