holder_rails 2.8.0 → 2.9.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: 3ce17582d2875c6a55f69eb0b88df95057f05b0e
4
- data.tar.gz: 3f98323d52721e5e86f86abb1ed8c6c62012a40e
3
+ metadata.gz: 39c5281d7180281efb41a1edf27f298bf8241a6e
4
+ data.tar.gz: 2dfc66e687c8ba89a0fa565c1fa1b88953c8fb58
5
5
  SHA512:
6
- metadata.gz: bcb36fed3e0cbd0c46ff569e3d249f08d790da9fd143eddb0189da38f6e7aa48985245a444ff7dcff0cd4147e522ba13d38034fcb5832860372aec055d4e4c4b
7
- data.tar.gz: ab5cca3f708a29f01abf1309ae402eac2eb12e9ea180af710acdb061a34c46abe9ac1731609b3dd63422404b6dce8742973816d22e2563a101a9a13daad5d3af
6
+ metadata.gz: 53a98257465f143b91d996abf4c6fd7bc44e459ddfb6f8aad46a77e57cc925f7cdedb18c3a165425504a5d042762586db5f643faeadb8f515f78ce1dbcd96898
7
+ data.tar.gz: 229d18c489cfd94a03ef253efa7d16045272d890a6f0853dcfb7c7239461367bc01480796a478d5cd6b7a55f82b502b1d36d609d5a68e1322c7154a6ec8b37ca
data/README.md CHANGED
@@ -39,19 +39,22 @@ You can use `holder_tag` helper in your views:
39
39
 
40
40
  ```ruby
41
41
  holder_tag 100
42
- # => <img data-src="holder.js/100x100/text:100x100/" src="" />
42
+ # => <img data-src="holder.js/100x100?" src="" />
43
43
 
44
44
  holder_tag '200x300'
45
- # => <img data-src="holder.js/200x300/text:200x300/" src="" />
45
+ # => <img data-src="holder.js/200x300?" src="" />
46
46
 
47
47
  holder_tag '200x300', 'Lorem ipsum'
48
- # => <img data-src="holder.js/200x300/text:Lorem ipsum/" src="" />
48
+ # => <img data-src="holder.js/200x300?text=Lorem ipsum" src="" />
49
49
 
50
50
  holder_tag '200x300', 'Lorem ipsum', 'social'
51
- # => <img data-src="holder.js/200x300/text:Lorem ipsum/social" src="" />
51
+ # => <img data-src="holder.js/200x300?text=Lorem ipsum&amp;theme=social" src="" />
52
52
 
53
- holder_tag '500x800', 'Example text', 'gray', :id => 'new', :class => 'special'
54
- # => <img class="special" data-src="holder.js/500x800/text:Example text/gray" id="new" src="" />
53
+ holder_tag '500x800', 'Example text', 'gray', id: 'new', class: 'special'
54
+ # => <img class="special" data-src="holder.js/500x800?text=Example text&amp;theme=gray" id="new" src="" />
55
+
56
+ holder_tag '500x800', 'Example text', 'gray', { id: 'new', class: 'special' }, { font: 'Helvetica' }
57
+ # => <img class="special" data-src="holder.js/500x800?font=Helvetica&amp;text=Example text&amp;theme=gray" id="new" src="" />
55
58
  ```
56
59
 
57
60
  For more information, check out [holder readme](https://github.com/imsky/holder#readme).
@@ -1,9 +1,13 @@
1
1
  module HolderRails
2
2
  module Helpers
3
- def holder_tag(size, text='', theme=nil, html_options={})
3
+ def holder_tag(size, text='', theme=nil, html_options={}, holder_options={})
4
4
  size = "#{size}x#{size}" unless size =~ /\A\d+%?x\d+\z/
5
- text = text.to_s.empty? ? size : text
6
- options = {:src => '', :data => {:src => "holder.js/#{size}/text:#{text}/#{theme}"}}
5
+
6
+ holder_options[:text] = text unless text.to_s.empty?
7
+ holder_options[:theme] = theme unless theme.nil?
8
+ holder_options = holder_options.map {|e| e.join('=') }.join('&')
9
+
10
+ options = { src: '', data: { src: "holder.js/#{size}?#{holder_options}" }}
7
11
  options = options.merge(html_options)
8
12
 
9
13
  tag :img, options
@@ -1,3 +1,3 @@
1
1
  module HolderRails
2
- VERSION = "2.8.0"
2
+ VERSION = "2.9.0"
3
3
  end
@@ -5,21 +5,26 @@ class HolderRailsTest < ActionView::TestCase
5
5
  include HolderRails::Helpers
6
6
 
7
7
  test "size" do
8
- assert_dom_equal '<img data-src="holder.js/100x100/text:100x100/" src="" />', holder_tag(100)
9
- assert_dom_equal '<img data-src="holder.js/200x300/text:200x300/" src="" />', holder_tag('200x300')
10
- assert_dom_equal '<img data-src="holder.js/100%x75/text:100%x75/" src="" />', holder_tag('100%x75')
8
+ assert_dom_equal '<img data-src="holder.js/100x100?" src="" />', holder_tag(100)
9
+ assert_dom_equal '<img data-src="holder.js/200x300?" src="" />', holder_tag('200x300')
10
+ assert_dom_equal '<img data-src="holder.js/100%x75?" src="" />', holder_tag('100%x75')
11
11
  end
12
12
 
13
13
  test "text" do
14
- assert_dom_equal '<img data-src="holder.js/200x300/text:Lorem ipsum/" src="" />', holder_tag('200x300', 'Lorem ipsum')
14
+ assert_dom_equal '<img data-src="holder.js/200x300?text=Lorem ipsum" src="" />', holder_tag('200x300', 'Lorem ipsum')
15
15
  end
16
16
 
17
17
  test "theme" do
18
- assert_dom_equal '<img data-src="holder.js/200x300/text:Lorem ipsum/social" src="" />', holder_tag('200x300', 'Lorem ipsum', 'social')
18
+ assert_dom_equal '<img data-src="holder.js/200x300?text=Lorem ipsum&amp;theme=social" src="" />', holder_tag('200x300', 'Lorem ipsum', 'social')
19
19
  end
20
20
 
21
21
  test "html_options" do
22
- assert_dom_equal '<img class="special" data-src="holder.js/500x800/text:Example text/gray" id="new" src="" />',
23
- holder_tag('500x800', 'Example text', 'gray', :id => 'new', :class => 'special')
22
+ assert_dom_equal '<img class="special" data-src="holder.js/500x800?text=Example text&amp;theme=gray" id="new" src="" />',
23
+ holder_tag('500x800', 'Example text', 'gray', id: 'new', class: 'special')
24
+ end
25
+
26
+ test "holder_options" do
27
+ assert_dom_equal '<img class="special" data-src="holder.js/500x800?font=Helvetica&amp;text=Example text&amp;theme=gray" id="new" src="" />',
28
+ holder_tag('500x800', 'Example text', 'gray', { id: 'new', class: 'special' }, { font: 'Helvetica' })
24
29
  end
25
30
  end
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
 
3
3
  Holder - client side image placeholders
4
- Version 2.8.0+7srgw
4
+ Version 2.9.0+f2dkw
5
5
  © 2015 Ivan Malopinsky - http://imsky.co
6
6
 
7
7
  Site: http://holderjs.com
@@ -64,6 +64,25 @@ License: MIT
64
64
  };
65
65
  }
66
66
 
67
+ // ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
68
+ // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
69
+ if (!Array.prototype.forEach) {
70
+ Array.prototype.forEach = function (fun /*, thisp */) {
71
+ if (this === void 0 || this === null) { throw TypeError(); }
72
+
73
+ var t = Object(this);
74
+ var len = t.length >>> 0;
75
+ if (typeof fun !== "function") { throw TypeError(); }
76
+
77
+ var thisp = arguments[1], i;
78
+ for (i = 0; i < len; i++) {
79
+ if (i in t) {
80
+ fun.call(thisp, t[i], i, t);
81
+ }
82
+ }
83
+ };
84
+ }
85
+
67
86
  //https://github.com/inexorabletash/polyfill/blob/master/web.js
68
87
  (function (global) {
69
88
  var B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
@@ -233,7 +252,7 @@ License: MIT
233
252
  if(typeof exports === 'object' && typeof module === 'object')
234
253
  module.exports = factory();
235
254
  else if(typeof define === 'function' && define.amd)
236
- define(factory);
255
+ define([], factory);
237
256
  else if(typeof exports === 'object')
238
257
  exports["Holder"] = factory();
239
258
  else
@@ -303,29 +322,27 @@ return /******/ (function(modules) { // webpackBootstrap
303
322
  */
304
323
 
305
324
  //Libraries and functions
306
- var onDomReady = __webpack_require__(3);
307
- var querystring = __webpack_require__(2);
325
+ var onDomReady = __webpack_require__(2);
326
+ var querystring = __webpack_require__(3);
327
+
328
+ var SceneGraph = __webpack_require__(6);
329
+ var utils = __webpack_require__(7);
330
+ var SVG = __webpack_require__(8);
331
+ var DOM = __webpack_require__(9);
332
+ var Color = __webpack_require__(10);
333
+ var constants = __webpack_require__(11);
308
334
 
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);
335
+ var svgRenderer = __webpack_require__(12);
336
+ var sgCanvasRenderer = __webpack_require__(15);
314
337
 
315
338
  var extend = utils.extend;
316
339
  var dimensionCheck = utils.dimensionCheck;
317
340
 
318
341
  //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';
342
+ var SVG_NS = constants.svg_ns;
326
343
 
327
344
  var Holder = {
328
- version: version,
345
+ version: constants.version,
329
346
 
330
347
  /**
331
348
  * Adds a theme to default settings
@@ -347,16 +364,14 @@ return /******/ (function(modules) { // webpackBootstrap
347
364
  */
348
365
  addImage: function(src, el) {
349
366
  //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
- }
367
+ var nodes = DOM.getNodeArray(el);
368
+ nodes.forEach(function (node) {
369
+ var img = DOM.newEl('img');
370
+ var domProps = {};
371
+ domProps[App.setup.dataAttr] = src;
372
+ DOM.setAttr(img, domProps);
373
+ node.appendChild(img);
374
+ });
360
375
  return this;
361
376
  },
362
377
 
@@ -389,7 +404,6 @@ return /******/ (function(modules) { // webpackBootstrap
389
404
 
390
405
  App.vars.preempted = true;
391
406
  App.vars.dataAttr = options.dataAttr || App.setup.dataAttr;
392
- App.vars.lineWrapRatio = options.lineWrapRatio || App.setup.lineWrapRatio;
393
407
 
394
408
  engineSettings.renderer = options.renderer ? options.renderer : App.setup.renderer;
395
409
  if (App.setup.renderers.join(',').indexOf(engineSettings.renderer) === -1) {
@@ -405,8 +419,7 @@ return /******/ (function(modules) { // webpackBootstrap
405
419
  engineSettings.svgXMLStylesheet = true;
406
420
  engineSettings.noFontFallback = options.noFontFallback ? options.noFontFallback : false;
407
421
 
408
- for (var i = 0; i < stylenodes.length; i++) {
409
- var styleNode = stylenodes[i];
422
+ stylenodes.forEach(function (styleNode) {
410
423
  if (styleNode.attributes.rel && styleNode.attributes.href && styleNode.attributes.rel.value == 'stylesheet') {
411
424
  var href = styleNode.attributes.href.value;
412
425
  //todo: write isomorphic relative-to-absolute URL function
@@ -415,26 +428,29 @@ return /******/ (function(modules) { // webpackBootstrap
415
428
  var stylesheetURL = proxyLink.protocol + '//' + proxyLink.host + proxyLink.pathname + proxyLink.search;
416
429
  engineSettings.stylesheets.push(stylesheetURL);
417
430
  }
418
- }
431
+ });
419
432
 
420
- for (i = 0; i < bgnodes.length; i++) {
433
+ bgnodes.forEach(function (bgNode) {
421
434
  //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');
435
+ if (!global.getComputedStyle) return;
436
+ var backgroundImage = global.getComputedStyle(bgNode, null).getPropertyValue('background-image');
437
+ var dataBackgroundImage = bgNode.getAttribute('data-background-src');
425
438
  var rawURL = dataBackgroundImage || backgroundImage;
426
439
 
427
440
  var holderURL = null;
428
- var holderString = '?' + options.domain + '/';
441
+ var holderString = options.domain + '/';
442
+ var holderStringIndex = rawURL.indexOf(holderString);
429
443
 
430
- if (rawURL.indexOf(holderString) === 0) {
444
+ if (holderStringIndex === 0) {
445
+ holderURL = rawURL;
446
+ } else if (holderStringIndex === 1 && rawURL[0] === '?') {
431
447
  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];
448
+ } else {
449
+ var fragment = rawURL.substr(holderStringIndex).match(/([^\"]*)"?\)/);
450
+ if (fragment !== null) {
451
+ holderURL = fragment[1];
452
+ } else if (rawURL.indexOf('url(') === 0) {
453
+ throw 'Holder: unable to parse background URL: ' + rawURL;
438
454
  }
439
455
  }
440
456
 
@@ -443,16 +459,15 @@ return /******/ (function(modules) { // webpackBootstrap
443
459
  if (holderFlags) {
444
460
  prepareDOMElement({
445
461
  mode: 'background',
446
- el: bgnodes[i],
462
+ el: bgNode,
447
463
  flags: holderFlags,
448
464
  engineSettings: engineSettings
449
465
  });
450
466
  }
451
467
  }
452
- }
468
+ });
453
469
 
454
- for (i = 0; i < objects.length; i++) {
455
- var object = objects[i];
470
+ objects.forEach(function (object) {
456
471
  var objectAttr = {};
457
472
 
458
473
  try {
@@ -468,10 +483,9 @@ return /******/ (function(modules) { // webpackBootstrap
468
483
  } else if (objectHasDataSrcURL) {
469
484
  prepareImageElement(options, engineSettings, objectAttr.dataSrc, object);
470
485
  }
471
- }
486
+ });
472
487
 
473
- for (i = 0; i < images.length; i++) {
474
- var image = images[i];
488
+ images.forEach(function (image) {
475
489
  var imageAttr = {};
476
490
 
477
491
  try {
@@ -506,7 +520,7 @@ return /******/ (function(modules) { // webpackBootstrap
506
520
  } else if (imageHasDataSrcURL) {
507
521
  prepareImageElement(options, engineSettings, imageAttr.dataSrc, image);
508
522
  }
509
- }
523
+ });
510
524
 
511
525
  return this;
512
526
  }
@@ -521,28 +535,28 @@ return /******/ (function(modules) { // webpackBootstrap
521
535
  stylenodes: 'head link.holderjs',
522
536
  themes: {
523
537
  'gray': {
524
- background: '#EEEEEE',
525
- foreground: '#AAAAAA'
538
+ bg: '#EEEEEE',
539
+ fg: '#AAAAAA'
526
540
  },
527
541
  'social': {
528
- background: '#3a5a97',
529
- foreground: '#FFFFFF'
542
+ bg: '#3a5a97',
543
+ fg: '#FFFFFF'
530
544
  },
531
545
  'industrial': {
532
- background: '#434A52',
533
- foreground: '#C2F200'
546
+ bg: '#434A52',
547
+ fg: '#C2F200'
534
548
  },
535
549
  'sky': {
536
- background: '#0D8FDB',
537
- foreground: '#FFFFFF'
550
+ bg: '#0D8FDB',
551
+ fg: '#FFFFFF'
538
552
  },
539
553
  'vine': {
540
- background: '#39DBAC',
541
- foreground: '#1E292C'
554
+ bg: '#39DBAC',
555
+ fg: '#1E292C'
542
556
  },
543
557
  'lava': {
544
- background: '#F8591A',
545
- foreground: '#1C2846'
558
+ bg: '#F8591A',
559
+ fg: '#1C2846'
546
560
  }
547
561
  }
548
562
  },
@@ -575,30 +589,19 @@ return /******/ (function(modules) { // webpackBootstrap
575
589
  }
576
590
 
577
591
  /**
578
- * Processes a Holder URL
592
+ * Processes a Holder URL and extracts configuration from query string
579
593
  *
580
594
  * @private
581
595
  * @param url URL
582
- * @param options Instance options from Holder.run
596
+ * @param instanceOptions Instance options from Holder.run
583
597
  */
584
- function parseURL(url, options) {
598
+ function parseURL(url, instanceOptions) {
585
599
  var holder = {
586
600
  theme: extend(App.settings.themes.gray, null),
587
- stylesheets: options.stylesheets,
588
- instanceOptions: options
601
+ stylesheets: instanceOptions.stylesheets,
602
+ instanceOptions: instanceOptions
589
603
  };
590
604
 
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
605
  var parts = url.split('?');
603
606
  var basics = parts[0].split('/');
604
607
 
@@ -622,11 +625,11 @@ return /******/ (function(modules) { // webpackBootstrap
622
625
  // Colors
623
626
 
624
627
  if (options.bg) {
625
- holder.theme.background = (options.bg.indexOf('#') === -1 ? '#' : '') + options.bg;
628
+ holder.theme.bg = utils.parseColor(options.bg);
626
629
  }
627
630
 
628
631
  if (options.fg) {
629
- holder.theme.foreground = (options.fg.indexOf('#') === -1 ? '#' : '') + options.fg;
632
+ holder.theme.fg = utils.parseColor(options.fg);
630
633
  }
631
634
 
632
635
  //todo: add automatic foreground to themes without foreground
@@ -660,6 +663,10 @@ return /******/ (function(modules) { // webpackBootstrap
660
663
  holder.align = options.align;
661
664
  }
662
665
 
666
+ if (options.lineWrap) {
667
+ holder.lineWrap = options.lineWrap;
668
+ }
669
+
663
670
  holder.nowrap = utils.truthy(options.nowrap);
664
671
 
665
672
  // Miscellaneous
@@ -693,6 +700,8 @@ return /******/ (function(modules) { // webpackBootstrap
693
700
  theme = flags.theme;
694
701
  var dimensionsCaption = dimensions.width + 'x' + dimensions.height;
695
702
  mode = mode == null ? (flags.fluid ? 'fluid' : 'image') : mode;
703
+ var holderTemplateRe = /holder_([a-z]+)/g;
704
+ var dimensionsInText = false;
696
705
 
697
706
  if (flags.text != null) {
698
707
  theme.text = flags.text;
@@ -707,12 +716,30 @@ return /******/ (function(modules) { // webpackBootstrap
707
716
  }
708
717
  }
709
718
 
719
+ if (theme.text) {
720
+ var holderTemplateMatches = theme.text.match(holderTemplateRe);
721
+
722
+ if (holderTemplateMatches !== null) {
723
+ //todo: optimize template replacement
724
+ holderTemplateMatches.forEach(function (match) {
725
+ if (match === 'holder_dimensions') {
726
+ theme.text = theme.text.replace(match, dimensionsCaption);
727
+ }
728
+ });
729
+ }
730
+ }
731
+
710
732
  var holderURL = flags.holderURL;
711
733
  var engineSettings = extend(_engineSettings, null);
712
734
 
713
735
  if (flags.font) {
736
+ /*
737
+ If external fonts are used in a <img> placeholder rendered with SVG, Holder falls back to canvas.
738
+
739
+ This is done because Firefox and Chrome disallow embedded SVGs from referencing external assets.
740
+ The workaround is either to change the placeholder tag from <img> to <object> or to use the canvas renderer.
741
+ */
714
742
  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
743
  if (!engineSettings.noFontFallback && el.nodeName.toLowerCase() === 'img' && App.setup.supportsCanvas && engineSettings.renderer === 'svg') {
717
744
  engineSettings = extend(engineSettings, {
718
745
  renderer: 'canvas'
@@ -747,7 +774,7 @@ return /******/ (function(modules) { // webpackBootstrap
747
774
 
748
775
  if (mode == 'image' || mode == 'fluid') {
749
776
  DOM.setAttr(el, {
750
- 'alt': (theme.text ? theme.text + ' [' + dimensionsCaption + ']' : dimensionsCaption)
777
+ 'alt': theme.text ? (dimensionsInText ? theme.text : theme.text + ' [' + dimensionsCaption + ']') : dimensionsCaption
751
778
  });
752
779
  }
753
780
 
@@ -763,10 +790,11 @@ return /******/ (function(modules) { // webpackBootstrap
763
790
  };
764
791
 
765
792
  if (mode == 'image') {
766
- if (engineSettings.renderer == 'html' || !flags.auto) {
793
+ if (!flags.auto) {
767
794
  el.style.width = dimensions.width + 'px';
768
795
  el.style.height = dimensions.height + 'px';
769
796
  }
797
+
770
798
  if (engineSettings.renderer == 'html') {
771
799
  el.style.backgroundColor = theme.background;
772
800
  } else {
@@ -849,7 +877,7 @@ return /******/ (function(modules) { // webpackBootstrap
849
877
  image = sgCanvasRenderer(sceneGraph, renderSettings);
850
878
  break;
851
879
  case 'svg':
852
- image = sgSVGRenderer(sceneGraph, renderSettings);
880
+ image = svgRenderer(sceneGraph, renderSettings);
853
881
  break;
854
882
  default:
855
883
  throw 'Holder: invalid renderer: ' + engineSettings.renderer;
@@ -875,14 +903,12 @@ return /******/ (function(modules) { // webpackBootstrap
875
903
  });
876
904
  } else if (el.nodeName.toLowerCase() === 'object') {
877
905
  DOM.setAttr(el, {
878
- 'data': image
879
- });
880
- DOM.setAttr(el, {
906
+ 'data': image,
881
907
  'type': 'image/svg+xml'
882
908
  });
883
909
  }
884
910
  if (engineSettings.reRender) {
885
- global.setTimeout(function() {
911
+ global.setTimeout(function () {
886
912
  var image = getRenderedImage();
887
913
  if (image == null) {
888
914
  throw 'Holder: couldn\'t render placeholder';
@@ -894,9 +920,7 @@ return /******/ (function(modules) { // webpackBootstrap
894
920
  });
895
921
  } else if (el.nodeName.toLowerCase() === 'object') {
896
922
  DOM.setAttr(el, {
897
- 'data': image
898
- });
899
- DOM.setAttr(el, {
923
+ 'data': image,
900
924
  'type': 'image/svg+xml'
901
925
  });
902
926
  }
@@ -927,7 +951,7 @@ return /******/ (function(modules) { // webpackBootstrap
927
951
 
928
952
  scene.font = {
929
953
  family: scene.theme.font ? scene.theme.font : 'Arial, Helvetica, Open Sans, sans-serif',
930
- size: textSize(scene.width, scene.height, fontSize),
954
+ size: textSize(scene.width, scene.height, fontSize, App.defaults.scale),
931
955
  units: scene.theme.units ? scene.theme.units : App.defaults.units,
932
956
  weight: scene.theme.fontweight ? scene.theme.fontweight : 'bold'
933
957
  };
@@ -948,6 +972,10 @@ return /******/ (function(modules) { // webpackBootstrap
948
972
  break;
949
973
  }
950
974
 
975
+ var lineWrap = scene.flags.lineWrap || App.setup.lineWrapRatio;
976
+ var sceneMargin = scene.width * lineWrap;
977
+ var maxLineWidth = sceneMargin;
978
+
951
979
  var sceneGraph = new SceneGraph({
952
980
  width: scene.width,
953
981
  height: scene.height
@@ -956,30 +984,29 @@ return /******/ (function(modules) { // webpackBootstrap
956
984
  var Shape = sceneGraph.Shape;
957
985
 
958
986
  var holderBg = new Shape.Rect('holderBg', {
959
- fill: scene.theme.background
987
+ fill: scene.theme.bg
960
988
  });
961
989
 
962
990
  holderBg.resize(scene.width, scene.height);
963
991
  sceneGraph.root.add(holderBg);
964
992
 
965
993
  if (scene.flags.outline) {
966
- //todo: generalize darken/lighten to more than RRGGBB hex values
967
994
  var outlineColor = new Color(holderBg.properties.fill);
968
-
969
995
  outlineColor = outlineColor.lighten(outlineColor.lighterThan('7f7f7f') ? -0.1 : 0.1);
970
-
971
996
  holderBg.properties.outline = {
972
997
  fill: outlineColor.toHex(true),
973
998
  width: 2
974
999
  };
975
1000
  }
976
1001
 
977
- var holderTextColor = scene.theme.foreground;
1002
+ var holderTextColor = scene.theme.fg;
978
1003
 
979
1004
  if (scene.flags.autoFg) {
980
1005
  var holderBgColor = new Color(holderBg.properties.fill);
981
1006
  var lightColor = new Color('fff');
982
- var darkColor = new Color('000', { 'alpha': 0.285714 });
1007
+ var darkColor = new Color('000', {
1008
+ 'alpha': 0.285714
1009
+ });
983
1010
 
984
1011
  holderTextColor = holderBgColor.blendAlpha(holderBgColor.lighterThan('7f7f7f') ? darkColor : lightColor).toHex(true);
985
1012
  }
@@ -1010,9 +1037,6 @@ return /******/ (function(modules) { // webpackBootstrap
1010
1037
  parent.height += line.height;
1011
1038
  }
1012
1039
 
1013
- var sceneMargin = scene.width * App.vars.lineWrapRatio;
1014
- var maxLineWidth = sceneMargin;
1015
-
1016
1040
  if (tpdata.lineCount > 1) {
1017
1041
  var offsetX = 0;
1018
1042
  var offsetY = 0;
@@ -1022,7 +1046,7 @@ return /******/ (function(modules) { // webpackBootstrap
1022
1046
 
1023
1047
  //Double margin so that left/right-aligned next is not flush with edge of image
1024
1048
  if (scene.align === 'left' || scene.align === 'right') {
1025
- maxLineWidth = scene.width * (1 - (1 - (App.vars.lineWrapRatio)) * 2);
1049
+ maxLineWidth = scene.width * (1 - (1 - lineWrap) * 2);
1026
1050
  }
1027
1051
 
1028
1052
  for (var i = 0; i < tpdata.words.length; i++) {
@@ -1101,15 +1125,16 @@ return /******/ (function(modules) { // webpackBootstrap
1101
1125
  * @param width Parent width
1102
1126
  * @param height Parent height
1103
1127
  * @param fontSize Requested text size
1128
+ * @param scale Proportional scale of text
1104
1129
  */
1105
- function textSize(width, height, fontSize) {
1130
+ function textSize(width, height, fontSize, scale) {
1106
1131
  var stageWidth = parseInt(width, 10);
1107
1132
  var stageHeight = parseInt(height, 10);
1108
1133
 
1109
1134
  var bigSide = Math.max(stageWidth, stageHeight);
1110
1135
  var smallSide = Math.min(stageWidth, stageHeight);
1111
1136
 
1112
- var newHeight = 0.8 * Math.min(smallSide, bigSide * App.defaults.scale);
1137
+ var newHeight = 0.8 * Math.min(smallSide, bigSide * scale);
1113
1138
  return Math.round(Math.max(fontSize, newHeight));
1114
1139
  }
1115
1140
 
@@ -1215,13 +1240,14 @@ return /******/ (function(modules) { // webpackBootstrap
1215
1240
  var renderableImages = [];
1216
1241
  var keys = Object.keys(App.vars.invisibleImages);
1217
1242
  var el;
1218
- for (var i = 0, l = keys.length; i < l; i++) {
1219
- el = App.vars.invisibleImages[keys[i]];
1243
+
1244
+ keys.forEach(function (key) {
1245
+ el = App.vars.invisibleImages[key];
1220
1246
  if (dimensionCheck(el) && el.nodeName.toLowerCase() == 'img') {
1221
1247
  renderableImages.push(el);
1222
- delete App.vars.invisibleImages[keys[i]];
1248
+ delete App.vars.invisibleImages[key];
1223
1249
  }
1224
- }
1250
+ });
1225
1251
 
1226
1252
  if (renderableImages.length) {
1227
1253
  Holder.run({
@@ -1229,7 +1255,10 @@ return /******/ (function(modules) { // webpackBootstrap
1229
1255
  });
1230
1256
  }
1231
1257
 
1232
- global.requestAnimationFrame(visibilityCheck);
1258
+ // Done to prevent 100% CPU usage via aggressive calling of requestAnimationFrame
1259
+ setTimeout(function () {
1260
+ global.requestAnimationFrame(visibilityCheck);
1261
+ }, 10);
1233
1262
  }
1234
1263
 
1235
1264
  /**
@@ -1312,7 +1341,7 @@ return /******/ (function(modules) { // webpackBootstrap
1312
1341
  var stagingTextBBox = stagingText.getBBox();
1313
1342
 
1314
1343
  //Get line count and split the string into words
1315
- var lineCount = Math.ceil(stagingTextBBox.width / (rootNode.properties.width * App.vars.lineWrapRatio));
1344
+ var lineCount = Math.ceil(stagingTextBBox.width / rootNode.properties.width);
1316
1345
  var words = htgProps.text.split(' ');
1317
1346
  var newlines = htgProps.text.match(/\\n/g);
1318
1347
  lineCount += newlines == null ? 0 : newlines.length;
@@ -1356,178 +1385,6 @@ return /******/ (function(modules) { // webpackBootstrap
1356
1385
  };
1357
1386
  })();
1358
1387
 
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
1388
  //Helpers
1532
1389
 
1533
1390
  /**
@@ -1575,10 +1432,6 @@ return /******/ (function(modules) { // webpackBootstrap
1575
1432
  renderers: ['html', 'canvas', 'svg']
1576
1433
  };
1577
1434
 
1578
- App.dpr = function(val) {
1579
- return val * App.setup.ratio;
1580
- };
1581
-
1582
1435
  //Properties modified during runtime
1583
1436
 
1584
1437
  App.vars = {
@@ -1594,27 +1447,15 @@ return /******/ (function(modules) { // webpackBootstrap
1594
1447
  //Pre-flight
1595
1448
 
1596
1449
  (function() {
1597
- var devicePixelRatio = 1,
1598
- backingStoreRatio = 1;
1599
-
1600
1450
  var canvas = DOM.newEl('canvas');
1601
- var ctx = null;
1602
1451
 
1603
1452
  if (canvas.getContext) {
1604
1453
  if (canvas.toDataURL('image/png').indexOf('data:image/png') != -1) {
1605
1454
  App.setup.renderer = 'canvas';
1606
- ctx = canvas.getContext('2d');
1607
1455
  App.setup.supportsCanvas = true;
1608
1456
  }
1609
1457
  }
1610
1458
 
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
1459
  if (!!document.createElementNS && !!document.createElementNS(SVG_NS, 'svg').createSVGRect) {
1619
1460
  App.setup.renderer = 'svg';
1620
1461
  App.setup.supportsSVG = true;
@@ -1650,131 +1491,23 @@ return /******/ (function(modules) { // webpackBootstrap
1650
1491
 
1651
1492
  /***/ },
1652
1493
  /* 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);
1494
+ /***/ function(module, exports) {
1667
1495
 
1668
- var arrayRegex = /(\w+)\[(\d+)\]/;
1669
- var objectRegex = /\w+\.\w+/;
1670
-
1671
- /**
1672
- * Parse the given query `str`.
1496
+ /*!
1497
+ * onDomReady.js 1.4.0 (c) 2013 Tubal Martin - MIT license
1673
1498
  *
1674
- * @param {String} str
1675
- * @return {Object}
1676
- * @api public
1499
+ * Specially modified to work with Holder.js
1677
1500
  */
1678
1501
 
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";
1502
+ function _onDomReady(win) {
1503
+ //Lazy loading fix for Firefox < 3.6
1504
+ //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
1505
+ if (document.readyState == null && document.addEventListener) {
1506
+ document.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
1507
+ document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
1508
+ document.readyState = "complete";
1509
+ }, false);
1510
+ document.readyState = "loading";
1778
1511
  }
1779
1512
 
1780
1513
  var doc = win.document,
@@ -1917,9 +1650,177 @@ return /******/ (function(modules) { // webpackBootstrap
1917
1650
  module.exports = typeof window !== "undefined" && _onDomReady(window);
1918
1651
 
1919
1652
  /***/ },
1920
- /* 4 */
1653
+ /* 3 */
1921
1654
  /***/ function(module, exports, __webpack_require__) {
1922
1655
 
1656
+ //Modified version of component/querystring
1657
+ //Changes: updated dependencies, dot notation parsing, JSHint fixes
1658
+ //Fork at https://github.com/imsky/querystring
1659
+
1660
+ /**
1661
+ * Module dependencies.
1662
+ */
1663
+
1664
+ var encode = encodeURIComponent;
1665
+ var decode = decodeURIComponent;
1666
+ var trim = __webpack_require__(4);
1667
+ var type = __webpack_require__(5);
1668
+
1669
+ var arrayRegex = /(\w+)\[(\d+)\]/;
1670
+ var objectRegex = /\w+\.\w+/;
1671
+
1672
+ /**
1673
+ * Parse the given query `str`.
1674
+ *
1675
+ * @param {String} str
1676
+ * @return {Object}
1677
+ * @api public
1678
+ */
1679
+
1680
+ exports.parse = function(str){
1681
+ if ('string' !== typeof str) return {};
1682
+
1683
+ str = trim(str);
1684
+ if ('' === str) return {};
1685
+ if ('?' === str.charAt(0)) str = str.slice(1);
1686
+
1687
+ var obj = {};
1688
+ var pairs = str.split('&');
1689
+ for (var i = 0; i < pairs.length; i++) {
1690
+ var parts = pairs[i].split('=');
1691
+ var key = decode(parts[0]);
1692
+ var m, ctx, prop;
1693
+
1694
+ if (m = arrayRegex.exec(key)) {
1695
+ obj[m[1]] = obj[m[1]] || [];
1696
+ obj[m[1]][m[2]] = decode(parts[1]);
1697
+ continue;
1698
+ }
1699
+
1700
+ if (m = objectRegex.test(key)) {
1701
+ m = key.split('.');
1702
+ ctx = obj;
1703
+
1704
+ while (m.length) {
1705
+ prop = m.shift();
1706
+
1707
+ if (!prop.length) continue;
1708
+
1709
+ if (!ctx[prop]) {
1710
+ ctx[prop] = {};
1711
+ } else if (ctx[prop] && typeof ctx[prop] !== 'object') {
1712
+ break;
1713
+ }
1714
+
1715
+ if (!m.length) {
1716
+ ctx[prop] = decode(parts[1]);
1717
+ }
1718
+
1719
+ ctx = ctx[prop];
1720
+ }
1721
+
1722
+ continue;
1723
+ }
1724
+
1725
+ obj[parts[0]] = null == parts[1] ? '' : decode(parts[1]);
1726
+ }
1727
+
1728
+ return obj;
1729
+ };
1730
+
1731
+ /**
1732
+ * Stringify the given `obj`.
1733
+ *
1734
+ * @param {Object} obj
1735
+ * @return {String}
1736
+ * @api public
1737
+ */
1738
+
1739
+ exports.stringify = function(obj){
1740
+ if (!obj) return '';
1741
+ var pairs = [];
1742
+
1743
+ for (var key in obj) {
1744
+ var value = obj[key];
1745
+
1746
+ if ('array' == type(value)) {
1747
+ for (var i = 0; i < value.length; ++i) {
1748
+ pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i]));
1749
+ }
1750
+ continue;
1751
+ }
1752
+
1753
+ pairs.push(encode(key) + '=' + encode(obj[key]));
1754
+ }
1755
+
1756
+ return pairs.join('&');
1757
+ };
1758
+
1759
+
1760
+ /***/ },
1761
+ /* 4 */
1762
+ /***/ function(module, exports) {
1763
+
1764
+
1765
+ exports = module.exports = trim;
1766
+
1767
+ function trim(str){
1768
+ return str.replace(/^\s*|\s*$/g, '');
1769
+ }
1770
+
1771
+ exports.left = function(str){
1772
+ return str.replace(/^\s*/, '');
1773
+ };
1774
+
1775
+ exports.right = function(str){
1776
+ return str.replace(/\s*$/, '');
1777
+ };
1778
+
1779
+
1780
+ /***/ },
1781
+ /* 5 */
1782
+ /***/ function(module, exports) {
1783
+
1784
+ /**
1785
+ * toString ref.
1786
+ */
1787
+
1788
+ var toString = Object.prototype.toString;
1789
+
1790
+ /**
1791
+ * Return the type of `val`.
1792
+ *
1793
+ * @param {Mixed} val
1794
+ * @return {String}
1795
+ * @api public
1796
+ */
1797
+
1798
+ module.exports = function(val){
1799
+ switch (toString.call(val)) {
1800
+ case '[object Date]': return 'date';
1801
+ case '[object RegExp]': return 'regexp';
1802
+ case '[object Arguments]': return 'arguments';
1803
+ case '[object Array]': return 'array';
1804
+ case '[object Error]': return 'error';
1805
+ }
1806
+
1807
+ if (val === null) return 'null';
1808
+ if (val === undefined) return 'undefined';
1809
+ if (val !== val) return 'nan';
1810
+ if (val && val.nodeType === 1) return 'element';
1811
+
1812
+ val = val.valueOf
1813
+ ? val.valueOf()
1814
+ : Object.prototype.valueOf.apply(val)
1815
+
1816
+ return typeof val;
1817
+ };
1818
+
1819
+
1820
+ /***/ },
1821
+ /* 6 */
1822
+ /***/ function(module, exports) {
1823
+
1923
1824
  var SceneGraph = function(sceneProperties) {
1924
1825
  var nodeCount = 1;
1925
1826
 
@@ -2028,10 +1929,10 @@ return /******/ (function(modules) { // webpackBootstrap
2028
1929
 
2029
1930
 
2030
1931
  /***/ },
2031
- /* 5 */
2032
- /***/ function(module, exports, __webpack_require__) {
1932
+ /* 7 */
1933
+ /***/ function(module, exports) {
2033
1934
 
2034
- /**
1935
+ /* WEBPACK VAR INJECTION */(function(global) {/**
2035
1936
  * Shallow object clone and merge
2036
1937
  *
2037
1938
  * @param a Object A
@@ -2148,12 +2049,69 @@ return /******/ (function(modules) { // webpackBootstrap
2148
2049
  return !!val;
2149
2050
  };
2150
2051
 
2052
+ /**
2053
+ * Parses input into a well-formed CSS color
2054
+ * @param val
2055
+ */
2056
+ exports.parseColor = function(val) {
2057
+ var hexre = /(^(?:#?)[0-9a-f]{6}$)|(^(?:#?)[0-9a-f]{3}$)/i;
2058
+ var rgbre = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;
2059
+ var rgbare = /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0\.\d{1,}|1)\)$/;
2060
+
2061
+ var match = val.match(hexre);
2062
+ var retval;
2063
+
2064
+ if (match !== null) {
2065
+ retval = match[1] || match[2];
2066
+ if (retval[0] !== '#') {
2067
+ return '#' + retval;
2068
+ } else {
2069
+ return retval;
2070
+ }
2071
+ }
2072
+
2073
+ match = val.match(rgbre);
2074
+
2075
+ if (match !== null) {
2076
+ retval = 'rgb(' + match.slice(1).join(',') + ')';
2077
+ return retval;
2078
+ }
2079
+
2080
+ match = val.match(rgbare);
2081
+
2082
+ if (match !== null) {
2083
+ retval = 'rgba(' + match.slice(1).join(',') + ')';
2084
+ return retval;
2085
+ }
2086
+
2087
+ return null;
2088
+ };
2089
+
2090
+ /**
2091
+ * Provides the correct scaling ratio for canvas drawing operations on HiDPI screens (e.g. Retina displays)
2092
+ */
2093
+ exports.canvasRatio = function () {
2094
+ var devicePixelRatio = 1;
2095
+ var backingStoreRatio = 1;
2096
+
2097
+ if (global.document) {
2098
+ var canvas = global.document.createElement('canvas');
2099
+ if (canvas.getContext) {
2100
+ var ctx = canvas.getContext('2d');
2101
+ devicePixelRatio = global.devicePixelRatio || 1;
2102
+ backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
2103
+ }
2104
+ }
2105
+
2106
+ return devicePixelRatio / backingStoreRatio;
2107
+ };
2108
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2151
2109
 
2152
2110
  /***/ },
2153
- /* 6 */
2111
+ /* 8 */
2154
2112
  /***/ function(module, exports, __webpack_require__) {
2155
2113
 
2156
- /* WEBPACK VAR INJECTION */(function(global) {var DOM = __webpack_require__(7);
2114
+ /* WEBPACK VAR INJECTION */(function(global) {var DOM = __webpack_require__(9);
2157
2115
 
2158
2116
  var SVG_NS = 'http://www.w3.org/2000/svg';
2159
2117
  var NODE_TYPE_COMMENT = 8;
@@ -2161,7 +2119,6 @@ return /******/ (function(modules) { // webpackBootstrap
2161
2119
  /**
2162
2120
  * Generic SVG element creation function
2163
2121
  *
2164
- * @private
2165
2122
  * @param svg SVG context, set to null if new
2166
2123
  * @param width Document width
2167
2124
  * @param height Document height
@@ -2227,7 +2184,7 @@ return /******/ (function(modules) { // webpackBootstrap
2227
2184
 
2228
2185
  return function(svgString, base64) {
2229
2186
  if (base64) {
2230
- return base64Prefix + btoa(unescape(encodeURIComponent(svgString)));
2187
+ return base64Prefix + btoa(global.unescape(encodeURIComponent(svgString)));
2231
2188
  } else {
2232
2189
  return rawPrefix + encodeURIComponent(svgString);
2233
2190
  }
@@ -2237,7 +2194,6 @@ return /******/ (function(modules) { // webpackBootstrap
2237
2194
  /**
2238
2195
  * Returns serialized SVG with XML processing instructions
2239
2196
  *
2240
- * @private
2241
2197
  * @param svg SVG context
2242
2198
  * @param stylesheets CSS stylesheets to include
2243
2199
  */
@@ -2268,13 +2224,12 @@ return /******/ (function(modules) { // webpackBootstrap
2268
2224
  /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2269
2225
 
2270
2226
  /***/ },
2271
- /* 7 */
2272
- /***/ function(module, exports, __webpack_require__) {
2227
+ /* 9 */
2228
+ /***/ function(module, exports) {
2273
2229
 
2274
2230
  /* WEBPACK VAR INJECTION */(function(global) {/**
2275
2231
  * Generic new DOM element function
2276
2232
  *
2277
- * @private
2278
2233
  * @param tag Tag to create
2279
2234
  * @param namespace Optional namespace value
2280
2235
  */
@@ -2282,20 +2237,19 @@ return /******/ (function(modules) { // webpackBootstrap
2282
2237
  if (!global.document) return;
2283
2238
 
2284
2239
  if (namespace == null) {
2285
- return document.createElement(tag);
2240
+ return global.document.createElement(tag);
2286
2241
  } else {
2287
- return document.createElementNS(namespace, tag);
2242
+ return global.document.createElementNS(namespace, tag);
2288
2243
  }
2289
2244
  };
2290
2245
 
2291
2246
  /**
2292
2247
  * Generic setAttribute function
2293
2248
  *
2294
- * @private
2295
2249
  * @param el Reference to DOM element
2296
2250
  * @param attrs Object with attribute keys and values
2297
2251
  */
2298
- exports.setAttr = function(el, attrs) {
2252
+ exports.setAttr = function (el, attrs) {
2299
2253
  for (var a in attrs) {
2300
2254
  el.setAttribute(a, attrs[a]);
2301
2255
  }
@@ -2330,20 +2284,26 @@ return /******/ (function(modules) { // webpackBootstrap
2330
2284
  } else if (val === null) {
2331
2285
  retval = [];
2332
2286
  }
2287
+
2288
+ retval = Array.prototype.slice.call(retval);
2289
+
2333
2290
  return retval;
2334
2291
  };
2335
2292
 
2336
2293
  /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2337
2294
 
2338
2295
  /***/ },
2339
- /* 8 */
2340
- /***/ function(module, exports, __webpack_require__) {
2296
+ /* 10 */
2297
+ /***/ function(module, exports) {
2341
2298
 
2342
- var Color = function (color, options) {
2343
- //todo: support array->color conversion
2299
+ var Color = function(color, options) {
2344
2300
  //todo: support rgba, hsla, and rrggbbaa notation
2301
+ //todo: use CIELAB internally
2302
+ //todo: add clamp function (with sign)
2345
2303
  if (typeof color !== 'string') return;
2346
2304
 
2305
+ this.original = color;
2306
+
2347
2307
  if (color.charAt(0) === '#') {
2348
2308
  color = color.slice(1);
2349
2309
  }
@@ -2358,58 +2318,119 @@ return /******/ (function(modules) { // webpackBootstrap
2358
2318
 
2359
2319
  this.alpha = 1;
2360
2320
 
2361
- if (options) {
2362
- this.alpha = options.alpha || this.alpha;
2321
+ if (options && options.alpha) {
2322
+ this.alpha = options.alpha;
2363
2323
  }
2364
2324
 
2365
- colorSet.call(this, parseInt(color, 16));
2325
+ this.set(parseInt(color, 16));
2366
2326
  };
2367
2327
 
2368
- Color.rgbToHex = function (r, g, b) {
2369
- return (((r | 0) << 16) + ((g | 0) << 8) + (b | 0)).toString(16);
2328
+ //todo: jsdocs
2329
+ Color.rgb2hex = function(r, g, b) {
2330
+ function format (decimal) {
2331
+ var hex = (decimal | 0).toString(16);
2332
+ if (decimal < 16) {
2333
+ hex = '0' + hex;
2334
+ }
2335
+ return hex;
2336
+ }
2337
+
2338
+ return [r, g, b].map(format).join('');
2339
+ };
2340
+
2341
+ //todo: jsdocs
2342
+ Color.hsl2rgb = function (h, s, l) {
2343
+ var H = h / 60;
2344
+ var C = (1 - Math.abs(2 * l - 1)) * s;
2345
+ var X = C * (1 - Math.abs(parseInt(H) % 2 - 1));
2346
+ var m = l - (C / 2);
2347
+
2348
+ var r = 0, g = 0, b = 0;
2349
+
2350
+ if (H >= 0 && H < 1) {
2351
+ r = C;
2352
+ g = X;
2353
+ } else if (H >= 1 && H < 2) {
2354
+ r = X;
2355
+ g = C;
2356
+ } else if (H >= 2 && H < 3) {
2357
+ g = C;
2358
+ b = X;
2359
+ } else if (H >= 3 && H < 4) {
2360
+ g = X;
2361
+ b = C;
2362
+ } else if (H >= 4 && H < 5) {
2363
+ r = X;
2364
+ b = C;
2365
+ } else if (H >= 5 && H < 6) {
2366
+ r = C;
2367
+ b = X;
2368
+ }
2369
+
2370
+ r += m;
2371
+ g += m;
2372
+ b += m;
2373
+
2374
+ r = parseInt(r * 255);
2375
+ g = parseInt(g * 255);
2376
+ b = parseInt(b * 255);
2377
+
2378
+ return [r, g, b];
2370
2379
  };
2371
2380
 
2372
2381
  /**
2373
2382
  * Sets the color from a raw RGB888 integer
2374
2383
  * @param raw RGB888 representation of color
2375
2384
  */
2376
- //todo: refactor into a more generic method
2377
- function colorSet (raw) {
2378
- this.rgb = {};
2379
- this.yuv = {};
2380
- this.raw = raw;
2385
+ //todo: refactor into a static method
2386
+ //todo: factor out individual color spaces
2387
+ //todo: add HSL, CIELAB, and CIELUV
2388
+ Color.prototype.set = function (val) {
2389
+ this.raw = val;
2381
2390
 
2382
- this.rgb.r = (raw & 0xFF0000) >> 16;
2383
- this.rgb.g = (raw & 0x00FF00) >> 8;
2384
- this.rgb.b = (raw & 0x0000FF);
2391
+ var r = (this.raw & 0xFF0000) >> 16;
2392
+ var g = (this.raw & 0x00FF00) >> 8;
2393
+ var b = (this.raw & 0x0000FF);
2385
2394
 
2386
2395
  // 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;
2396
+ var y = 0.2126 * r + 0.7152 * g + 0.0722 * b;
2397
+ var u = -0.09991 * r - 0.33609 * g + 0.436 * b;
2398
+ var v = 0.615 * r - 0.55861 * g - 0.05639 * b;
2399
+
2400
+ this.rgb = {
2401
+ r: r,
2402
+ g: g,
2403
+ b: b
2404
+ };
2405
+
2406
+ this.yuv = {
2407
+ y: y,
2408
+ u: u,
2409
+ v: v
2410
+ };
2390
2411
 
2391
2412
  return this;
2392
- }
2413
+ };
2393
2414
 
2394
2415
  /**
2395
2416
  * Lighten or darken a color
2396
2417
  * @param multiplier Amount to lighten or darken (-1 to 1)
2397
2418
  */
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));
2419
+ Color.prototype.lighten = function(multiplier) {
2420
+ var cm = Math.min(1, Math.max(0, Math.abs(multiplier))) * (multiplier < 0 ? -1 : 1);
2421
+ var bm = (255 * cm) | 0;
2422
+ var cr = Math.min(255, Math.max(0, this.rgb.r + bm));
2423
+ var cg = Math.min(255, Math.max(0, this.rgb.g + bm));
2424
+ var cb = Math.min(255, Math.max(0, this.rgb.b + bm));
2425
+ var hex = Color.rgb2hex(cr, cg, cb);
2426
+ return new Color(hex);
2406
2427
  };
2407
2428
 
2408
2429
  /**
2409
2430
  * Output color in hex format
2410
2431
  * @param addHash Add a hash character to the beginning of the output
2411
- */
2412
- Color.prototype.toHex = function (addHash) {
2432
+ */
2433
+ Color.prototype.toHex = function(addHash) {
2413
2434
  return (addHash ? '#' : '') + this.raw.toString(16);
2414
2435
  };
2415
2436
 
@@ -2417,7 +2438,7 @@ return /******/ (function(modules) { // webpackBootstrap
2417
2438
  * Returns whether or not current color is lighter than another color
2418
2439
  * @param color Color to compare against
2419
2440
  */
2420
- Color.prototype.lighterThan = function (color) {
2441
+ Color.prototype.lighterThan = function(color) {
2421
2442
  if (!(color instanceof Color)) {
2422
2443
  color = new Color(color);
2423
2444
  }
@@ -2430,7 +2451,7 @@ return /******/ (function(modules) { // webpackBootstrap
2430
2451
  * @param color Color to mix with
2431
2452
  * @param multiplier How much to mix with the other color
2432
2453
  */
2433
- /*
2454
+ /*
2434
2455
  Color.prototype.mix = function (color, multiplier) {
2435
2456
  if (!(color instanceof Color)) {
2436
2457
  color = new Color(color);
@@ -2459,8 +2480,8 @@ return /******/ (function(modules) { // webpackBootstrap
2459
2480
  * Returns the result of blending another color on top of current color with alpha
2460
2481
  * @param color Color to blend on top of current color, i.e. "Ca"
2461
2482
  */
2462
- //todo: see if .blendAlpha can be merged into .mix
2463
- Color.prototype.blendAlpha = function (color) {
2483
+ //todo: see if .blendAlpha can be merged into .mix
2484
+ Color.prototype.blendAlpha = function(color) {
2464
2485
  if (!(color instanceof Color)) {
2465
2486
  color = new Color(color);
2466
2487
  }
@@ -2473,71 +2494,480 @@ return /******/ (function(modules) { // webpackBootstrap
2473
2494
  var g = Ca.alpha * Ca.rgb.g + (1 - Ca.alpha) * Cb.rgb.g;
2474
2495
  var b = Ca.alpha * Ca.rgb.b + (1 - Ca.alpha) * Cb.rgb.b;
2475
2496
 
2476
- return new Color(Color.rgbToHex(r, g, b));
2497
+ return new Color(Color.rgb2hex(r, g, b));
2477
2498
  };
2478
2499
 
2479
2500
  module.exports = Color;
2480
2501
 
2481
2502
 
2482
2503
  /***/ },
2483
- /* 9 */
2504
+ /* 11 */
2505
+ /***/ function(module, exports) {
2506
+
2507
+ module.exports = {
2508
+ 'version': '2.9.0',
2509
+ 'svg_ns': 'http://www.w3.org/2000/svg'
2510
+ };
2511
+
2512
+ /***/ },
2513
+ /* 12 */
2514
+ /***/ function(module, exports, __webpack_require__) {
2515
+
2516
+ var shaven = __webpack_require__(13);
2517
+
2518
+ var SVG = __webpack_require__(8);
2519
+ var constants = __webpack_require__(11);
2520
+ var utils = __webpack_require__(7);
2521
+
2522
+ var SVG_NS = constants.svg_ns;
2523
+
2524
+ var templates = {
2525
+ 'element': function (options) {
2526
+ var tag = options.tag;
2527
+ var content = options.content || '';
2528
+ delete options.tag;
2529
+ delete options.content;
2530
+ return [tag, content, options];
2531
+ }
2532
+ };
2533
+
2534
+ //todo: deprecate tag arg, infer tag from shape object
2535
+ function convertShape (shape, tag) {
2536
+ return templates.element({
2537
+ 'tag': tag,
2538
+ 'width': shape.width,
2539
+ 'height': shape.height,
2540
+ 'fill': shape.properties.fill
2541
+ });
2542
+ }
2543
+
2544
+ function textCss (properties) {
2545
+ return utils.cssProps({
2546
+ 'fill': properties.fill,
2547
+ 'font-weight': properties.font.weight,
2548
+ 'font-family': properties.font.family + ', monospace',
2549
+ 'font-size': properties.font.size + properties.font.units
2550
+ });
2551
+ }
2552
+
2553
+ function outlinePath (bgWidth, bgHeight, outlineWidth) {
2554
+ var outlineOffsetWidth = outlineWidth / 2;
2555
+
2556
+ return [
2557
+ 'M', outlineOffsetWidth, outlineOffsetWidth,
2558
+ 'H', bgWidth - outlineOffsetWidth,
2559
+ 'V', bgHeight - outlineOffsetWidth,
2560
+ 'H', outlineOffsetWidth,
2561
+ 'V', 0,
2562
+ 'M', 0, outlineOffsetWidth,
2563
+ 'L', bgWidth, bgHeight - outlineOffsetWidth,
2564
+ 'M', 0, bgHeight - outlineOffsetWidth,
2565
+ 'L', bgWidth, outlineOffsetWidth
2566
+ ].join(' ');
2567
+ }
2568
+
2569
+ module.exports = function (sceneGraph, renderSettings) {
2570
+ var engineSettings = renderSettings.engineSettings;
2571
+ var stylesheets = engineSettings.stylesheets;
2572
+ var stylesheetXml = stylesheets.map(function (stylesheet) {
2573
+ return '<?xml-stylesheet rel="stylesheet" href="' + stylesheet + '"?>';
2574
+ }).join('\n');
2575
+
2576
+ var holderId = 'holder_' + Number(new Date()).toString(16);
2577
+
2578
+ var root = sceneGraph.root;
2579
+ var textGroup = root.children.holderTextGroup;
2580
+
2581
+ var css = '#' + holderId + ' text { ' + textCss(textGroup.properties) + ' } ';
2582
+
2583
+ // push text down to be equally vertically aligned with canvas renderer
2584
+ textGroup.y += textGroup.textPositionData.boundingBox.height * 0.8;
2585
+
2586
+ var wordTags = [];
2587
+
2588
+ Object.keys(textGroup.children).forEach(function (lineKey) {
2589
+ var line = textGroup.children[lineKey];
2590
+
2591
+ Object.keys(line.children).forEach(function (wordKey) {
2592
+ var word = line.children[wordKey];
2593
+ var x = textGroup.x + line.x + word.x;
2594
+ var y = textGroup.y + line.y + word.y;
2595
+
2596
+ var wordTag = templates.element({
2597
+ 'tag': 'text',
2598
+ 'content': word.properties.text,
2599
+ 'x': x,
2600
+ 'y': y
2601
+ });
2602
+
2603
+ wordTags.push(wordTag);
2604
+ });
2605
+ });
2606
+
2607
+ var text = templates.element({
2608
+ 'tag': 'g',
2609
+ 'content': wordTags
2610
+ });
2611
+
2612
+ var outline = null;
2613
+
2614
+ if (root.children.holderBg.properties.outline) {
2615
+ var outlineProperties = root.children.holderBg.properties.outline;
2616
+ outline = templates.element({
2617
+ 'tag': 'path',
2618
+ 'd': outlinePath(root.children.holderBg.width, root.children.holderBg.height, outlineProperties.width),
2619
+ 'stroke-width': outlineProperties.width,
2620
+ 'stroke': outlineProperties.fill,
2621
+ 'fill': 'none'
2622
+ });
2623
+ }
2624
+
2625
+ var bg = convertShape(root.children.holderBg, 'rect');
2626
+
2627
+ var sceneContent = [];
2628
+
2629
+ sceneContent.push(bg);
2630
+ if (outlineProperties) {
2631
+ sceneContent.push(outline);
2632
+ }
2633
+ sceneContent.push(text);
2634
+
2635
+ var scene = templates.element({
2636
+ 'tag': 'g',
2637
+ 'id': holderId,
2638
+ 'content': sceneContent
2639
+ });
2640
+
2641
+ var style = templates.element({
2642
+ 'tag': 'style',
2643
+ //todo: figure out how to add CDATA directive
2644
+ 'content': css,
2645
+ 'type': 'text/css'
2646
+ });
2647
+
2648
+ var defs = templates.element({
2649
+ 'tag': 'defs',
2650
+ 'content': style
2651
+ });
2652
+
2653
+ var svg = templates.element({
2654
+ 'tag': 'svg',
2655
+ 'content': [defs, scene],
2656
+ 'width': root.properties.width,
2657
+ 'height': root.properties.height,
2658
+ 'xmlns': SVG_NS,
2659
+ 'viewBox': [0, 0, root.properties.width, root.properties.height].join(' '),
2660
+ 'preserveAspectRatio': 'none'
2661
+ });
2662
+
2663
+ var output = shaven(svg);
2664
+
2665
+ output = stylesheetXml + output[0];
2666
+
2667
+ var svgString = SVG.svgStringToDataURI(output, renderSettings.mode === 'background');
2668
+ return svgString;
2669
+ };
2670
+
2671
+ /***/ },
2672
+ /* 13 */
2484
2673
  /***/ function(module, exports, __webpack_require__) {
2485
2674
 
2675
+ var escape = __webpack_require__(14)
2676
+
2677
+ // TODO: remove namespace
2678
+
2679
+ module.exports = function shaven (array, namespace, returnObject) {
2680
+
2681
+ 'use strict'
2682
+
2683
+ var i = 1,
2684
+ doesEscape = true,
2685
+ HTMLString,
2686
+ attributeKey,
2687
+ callback,
2688
+ key
2689
+
2690
+
2691
+ returnObject = returnObject || {}
2692
+
2693
+
2694
+ function createElement (sugarString) {
2695
+
2696
+ var tags = sugarString.match(/^\w+/),
2697
+ element = {
2698
+ tag: tags ? tags[0] : 'div',
2699
+ attr: {},
2700
+ children: []
2701
+ },
2702
+ id = sugarString.match(/#([\w-]+)/),
2703
+ reference = sugarString.match(/\$([\w-]+)/),
2704
+ classNames = sugarString.match(/\.[\w-]+/g)
2705
+
2706
+
2707
+ // Assign id if is set
2708
+ if (id) {
2709
+ element.attr.id = id[1]
2710
+
2711
+ // Add element to the return object
2712
+ returnObject[id[1]] = element
2713
+ }
2714
+
2715
+ if (reference)
2716
+ returnObject[reference[1]] = element
2717
+
2718
+ if (classNames)
2719
+ element.attr.class = classNames.join(' ').replace(/\./g, '')
2720
+
2721
+ if (sugarString.match(/&$/g))
2722
+ doesEscape = false
2723
+
2724
+ return element
2725
+ }
2726
+
2727
+ function replacer (key, value) {
2728
+
2729
+ if (value === null || value === false || value === undefined)
2730
+ return
2731
+
2732
+ if (typeof value !== 'string' && typeof value !== 'object')
2733
+ return String(value)
2734
+
2735
+ return value
2736
+ }
2737
+
2738
+ function escapeAttribute (string) {
2739
+ return String(string)
2740
+ .replace(/&/g, '&amp;')
2741
+ .replace(/"/g, '&quot;')
2742
+ }
2743
+
2744
+ function escapeHTML (string) {
2745
+ return String(string)
2746
+ .replace(/&/g, '&amp;')
2747
+ .replace(/"/g, '&quot;')
2748
+ .replace(/'/g, '&apos;')
2749
+ .replace(/</g, '&lt;')
2750
+ .replace(/>/g, '&gt;')
2751
+ }
2752
+
2753
+
2754
+ if (typeof array[0] === 'string')
2755
+ array[0] = createElement(array[0])
2756
+
2757
+ else if (Array.isArray(array[0]))
2758
+ i = 0
2759
+
2760
+ else
2761
+ throw new Error(
2762
+ 'First element of array must be a string, ' +
2763
+ 'or an array and not ' + JSON.stringify(array[0])
2764
+ )
2765
+
2766
+
2767
+ for (; i < array.length; i++) {
2768
+
2769
+ // Don't render element if value is false or null
2770
+ if (array[i] === false || array[i] === null) {
2771
+ array[0] = false
2772
+ break
2773
+ }
2774
+
2775
+ // Continue with next array value if current value is undefined or true
2776
+ else if (array[i] === undefined || array[i] === true) {
2777
+ continue
2778
+ }
2779
+
2780
+ else if (typeof array[i] === 'string') {
2781
+ if (doesEscape)
2782
+ array[i] = escapeHTML(array[i])
2783
+
2784
+ array[0].children.push(array[i])
2785
+ }
2786
+
2787
+ else if (typeof array[i] === 'number') {
2788
+
2789
+ array[0].children.push(array[i])
2790
+ }
2791
+
2792
+ else if (Array.isArray(array[i])) {
2793
+
2794
+ if (Array.isArray(array[i][0])) {
2795
+ array[i].reverse().forEach(function (subArray) {
2796
+ array.splice(i + 1, 0, subArray)
2797
+ })
2798
+
2799
+ if (i !== 0)
2800
+ continue
2801
+ i++
2802
+ }
2803
+
2804
+ shaven(array[i], namespace, returnObject)
2805
+
2806
+ if (array[i][0])
2807
+ array[0].children.push(array[i][0])
2808
+ }
2809
+
2810
+ else if (typeof array[i] === 'function')
2811
+ callback = array[i]
2812
+
2813
+
2814
+ else if (typeof array[i] === 'object') {
2815
+ for (attributeKey in array[i])
2816
+ if (array[i].hasOwnProperty(attributeKey))
2817
+ if (array[i][attributeKey] !== null &&
2818
+ array[i][attributeKey] !== false)
2819
+ if (attributeKey === 'style' &&
2820
+ typeof array[i][attributeKey] === 'object')
2821
+ array[0].attr[attributeKey] = JSON
2822
+ .stringify(array[i][attributeKey], replacer)
2823
+ .slice(2, -2)
2824
+ .replace(/","/g, ';')
2825
+ .replace(/":"/g, ':')
2826
+ .replace(/\\"/g, '\'')
2827
+
2828
+ else
2829
+ array[0].attr[attributeKey] = array[i][attributeKey]
2830
+ }
2831
+
2832
+ else
2833
+ throw new TypeError('"' + array[i] + '" is not allowed as a value.')
2834
+ }
2835
+
2836
+
2837
+ if (array[0] !== false) {
2838
+
2839
+ HTMLString = '<' + array[0].tag
2840
+
2841
+ for (key in array[0].attr)
2842
+ if (array[0].attr.hasOwnProperty(key))
2843
+ HTMLString += ' ' + key + '="' +
2844
+ escapeAttribute(array[0].attr[key] || '') + '"'
2845
+
2846
+ HTMLString += '>'
2847
+
2848
+ array[0].children.forEach(function (child) {
2849
+ HTMLString += child
2850
+ })
2851
+
2852
+ HTMLString += '</' + array[0].tag + '>'
2853
+
2854
+ array[0] = HTMLString
2855
+ }
2856
+
2857
+ // Return root element on index 0
2858
+ returnObject[0] = array[0]
2859
+
2860
+ if (callback)
2861
+ callback(array[0])
2862
+
2863
+ // returns object containing all elements with an id and the root element
2864
+ return returnObject
2865
+ }
2866
+
2867
+
2868
+ /***/ },
2869
+ /* 14 */
2870
+ /***/ function(module, exports) {
2871
+
2872
+ /*!
2873
+ * escape-html
2874
+ * Copyright(c) 2012-2013 TJ Holowaychuk
2875
+ * MIT Licensed
2876
+ */
2877
+
2486
2878
  /**
2487
- * toString ref.
2879
+ * Module exports.
2880
+ * @public
2488
2881
  */
2489
2882
 
2490
- var toString = Object.prototype.toString;
2883
+ module.exports = escapeHtml;
2491
2884
 
2492
2885
  /**
2493
- * Return the type of `val`.
2886
+ * Escape special characters in the given string of html.
2494
2887
  *
2495
- * @param {Mixed} val
2496
- * @return {String}
2497
- * @api public
2888
+ * @param {string} str The string to escape for inserting into HTML
2889
+ * @return {string}
2890
+ * @public
2498
2891
  */
2499
2892
 
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
- }
2893
+ function escapeHtml(html) {
2894
+ return String(html)
2895
+ .replace(/&/g, '&amp;')
2896
+ .replace(/"/g, '&quot;')
2897
+ .replace(/'/g, '&#39;')
2898
+ .replace(/</g, '&lt;')
2899
+ .replace(/>/g, '&gt;');
2900
+ }
2508
2901
 
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
2902
 
2514
- val = val.valueOf
2515
- ? val.valueOf()
2516
- : Object.prototype.valueOf.apply(val)
2903
+ /***/ },
2904
+ /* 15 */
2905
+ /***/ function(module, exports, __webpack_require__) {
2517
2906
 
2518
- return typeof val;
2519
- };
2907
+ var DOM = __webpack_require__(9);
2908
+ var utils = __webpack_require__(7);
2520
2909
 
2910
+ module.exports = (function() {
2911
+ var canvas = DOM.newEl('canvas');
2912
+ var ctx = null;
2521
2913
 
2522
- /***/ },
2523
- /* 10 */
2524
- /***/ function(module, exports, __webpack_require__) {
2914
+ return function(sceneGraph) {
2915
+ if (ctx == null) {
2916
+ ctx = canvas.getContext('2d');
2917
+ }
2525
2918
 
2526
-
2527
- exports = module.exports = trim;
2919
+ var dpr = utils.canvasRatio();
2920
+ var root = sceneGraph.root;
2921
+ canvas.width = dpr * root.properties.width;
2922
+ canvas.height = dpr * root.properties.height ;
2923
+ ctx.textBaseline = 'middle';
2528
2924
 
2529
- function trim(str){
2530
- return str.replace(/^\s*|\s*$/g, '');
2531
- }
2925
+ var bg = root.children.holderBg;
2926
+ var bgWidth = dpr * bg.width;
2927
+ var bgHeight = dpr * bg.height;
2928
+ //todo: parametrize outline width (e.g. in scene object)
2929
+ var outlineWidth = 2;
2930
+ var outlineOffsetWidth = outlineWidth / 2;
2532
2931
 
2533
- exports.left = function(str){
2534
- return str.replace(/^\s*/, '');
2535
- };
2932
+ ctx.fillStyle = bg.properties.fill;
2933
+ ctx.fillRect(0, 0, bgWidth, bgHeight);
2536
2934
 
2537
- exports.right = function(str){
2538
- return str.replace(/\s*$/, '');
2539
- };
2935
+ if (bg.properties.outline) {
2936
+ //todo: abstract this into a method
2937
+ ctx.strokeStyle = bg.properties.outline.fill;
2938
+ ctx.lineWidth = bg.properties.outline.width;
2939
+ ctx.moveTo(outlineOffsetWidth, outlineOffsetWidth);
2940
+ // TL, TR, BR, BL
2941
+ ctx.lineTo(bgWidth - outlineOffsetWidth, outlineOffsetWidth);
2942
+ ctx.lineTo(bgWidth - outlineOffsetWidth, bgHeight - outlineOffsetWidth);
2943
+ ctx.lineTo(outlineOffsetWidth, bgHeight - outlineOffsetWidth);
2944
+ ctx.lineTo(outlineOffsetWidth, outlineOffsetWidth);
2945
+ // Diagonals
2946
+ ctx.moveTo(0, outlineOffsetWidth);
2947
+ ctx.lineTo(bgWidth, bgHeight - outlineOffsetWidth);
2948
+ ctx.moveTo(0, bgHeight - outlineOffsetWidth);
2949
+ ctx.lineTo(bgWidth, outlineOffsetWidth);
2950
+ ctx.stroke();
2951
+ }
2540
2952
 
2953
+ var textGroup = root.children.holderTextGroup;
2954
+ ctx.font = textGroup.properties.font.weight + ' ' + (dpr * textGroup.properties.font.size) + textGroup.properties.font.units + ' ' + textGroup.properties.font.family + ', monospace';
2955
+ ctx.fillStyle = textGroup.properties.fill;
2956
+
2957
+ for (var lineKey in textGroup.children) {
2958
+ var line = textGroup.children[lineKey];
2959
+ for (var wordKey in line.children) {
2960
+ var word = line.children[wordKey];
2961
+ var x = dpr * (textGroup.x + line.x + word.x);
2962
+ var y = dpr * (textGroup.y + line.y + word.y + (textGroup.properties.leading / 2));
2963
+
2964
+ ctx.fillText(word.properties.text, x, y);
2965
+ }
2966
+ }
2967
+
2968
+ return canvas.toDataURL('image/png');
2969
+ };
2970
+ })();
2541
2971
 
2542
2972
  /***/ }
2543
2973
  /******/ ])