holder_rails 2.8.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
  /******/ ])