inkcite 1.13.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -72,9 +72,15 @@ module Inkcite
72
72
  width = opt[:width]
73
73
  element[:width] = width unless width.blank?
74
74
 
75
+ mobile_width = opt[MOBILE_WIDTH]
76
+ element.mobile_style[:width] = px(mobile_width) unless none?(mobile_width)
77
+
75
78
  height = opt[:height].to_i
76
79
  element[:height] = height if height > 0
77
80
 
81
+ mobile_height = opt[MOBILE_HEIGHT]
82
+ element.mobile_style[:height] = px(mobile_height) unless none?(mobile_height)
83
+
78
84
  end
79
85
 
80
86
  private
@@ -75,8 +75,10 @@ module Inkcite
75
75
  # If the table defines mobile-padding, then apply the correct mobile
76
76
  # style to this td - and its !important if there is padding on
77
77
  # the td already.
78
- mobile_padding = table_opt[MOBILE_PADDING] || opt[MOBILE_PADDING]
79
- td.mobile_style[:padding] = px(mobile_padding) unless mobile_padding.blank? || mobile == HIDE
78
+ unless mobile == HIDE
79
+ mix_mobile_padding td, table_opt, ctx
80
+ mix_mobile_padding td, opt, ctx
81
+ end
80
82
 
81
83
  # Need to handle Fluid-Drop HTML injection here before the rest of the
82
84
  # TD is formalized. Fluid-Drop removes the width attribute of the cell
@@ -147,6 +149,9 @@ module Inkcite
147
149
 
148
150
  end
149
151
 
152
+ # Support custom alignment on mobile devices
153
+ mix_mobile_text_align td, opt, ctx
154
+
150
155
  rowspan = opt[:rowspan].to_i
151
156
  td[:rowspan] = rowspan if rowspan > 0
152
157
 
@@ -24,6 +24,11 @@ module Inkcite
24
24
  # image name is frozen to ensure it isn't modified later.
25
25
  src = opt[:src].freeze
26
26
 
27
+ # This will hold the names of each of the frames, sans any full
28
+ # URL qualification (e.g. images/) These are interpolated to
29
+ # include the index
30
+ frame_srcs = []
31
+
27
32
  # This will hold all frame source file names interpolated to include
28
33
  # index (e.g. %1 being replaced with the frame number, if present).
29
34
  frames = []
@@ -40,6 +45,7 @@ module Inkcite
40
45
  # this loop also verifies that the referenced image exists.
41
46
  frame_count.times do |n|
42
47
  frame_src = src.gsub('%1', "#{n + 1}")
48
+ frame_srcs << frame_src
43
49
  frames << image_url(frame_src, opt, ctx, false, false)
44
50
  end
45
51
 
@@ -91,7 +97,7 @@ module Inkcite
91
97
  html << Element.new('a', { :id => id, :href => quote(href), :class => hover_klass, :bgcolor => bgcolor, :bggradient => gradient, :block => true }).to_helper
92
98
 
93
99
  table = Element.new('table', {
94
- :width => '100%', :background => first_frame, BACKGROUND_SIZE => 'cover',
100
+ :width => '100%', :background => frame_srcs[0], BACKGROUND_SIZE => 'cover',
95
101
  Table::TR_TRANSITION => %q("all .5s cubic-bezier(0.075, 0.82, 0.165, 1)")
96
102
  })
97
103
 
@@ -52,6 +52,62 @@ module Inkcite
52
52
  end
53
53
  end
54
54
 
55
+ # Conversion of HSL to RGB color courtesy of
56
+ # http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
57
+ def self.hsl_to_color h, s, l
58
+
59
+ # The algorithm expects h, s and l to be values between 0-1.
60
+ h = h / 360.0
61
+ s = s / 100.0
62
+ l = l / 100.0
63
+
64
+ # Wrap the color wheel if the hue provided is less than or
65
+ # greater than 1
66
+ h += 1.0 while h < 0
67
+ h -= 1.0 while h > 1
68
+
69
+ s = 0.0 if s < 0
70
+ s = 1.0 if s > 1
71
+
72
+ l = 0.0 if l < 0
73
+ l = 1.0 if l > 1
74
+
75
+ r = g = b = 0
76
+
77
+ if s == 0
78
+ r = g = b = l
79
+
80
+ else
81
+ q = l < 0.5 ? l * (1 + s) : l + s - l * s
82
+ p = 2 * l - q
83
+ r = hue_to_rgb(p, q, h + 1/3.0)
84
+ g = hue_to_rgb(p, q, h)
85
+ b = hue_to_rgb(p, q, h - 1/3.0)
86
+
87
+ end
88
+
89
+ r = (r * 255).round(0)
90
+ g = (g * 255).round(0)
91
+ b = (b * 255).round(0)
92
+
93
+ "##{rgb_to_hex(r)}#{rgb_to_hex(g)}#{rgb_to_hex(b)}"
94
+ end
95
+
96
+ def self.hue_to_rgb p, q, t
97
+ t += 1 if t < 0
98
+ t -= 1 if t > 1
99
+ return (p + (q - p) * 6.0 * t) if (t < 1.0/6.0)
100
+ return q if (t < 0.5)
101
+ return (p + (q - p) * (2/3.0 - t) * 6) if (t < 2/3.0)
102
+ p
103
+ end
104
+
105
+ # RGB to hex courtesy of
106
+ # http://blog.lebrijo.com/converting-rgb-colors-to-hexadecimal-with-ruby/
107
+ def self.rgb_to_hex val
108
+ val.to_s(16).rjust(2, '0').downcase
109
+ end
110
+
55
111
  def self.lighten color, amount=0.6
56
112
  return WHITE if color.nil?
57
113
  rgb = color.gsub('#', '').scan(/../).map { |c| c.hex }
@@ -1,3 +1,3 @@
1
1
  module Inkcite
2
- VERSION = "1.13.0"
2
+ VERSION = "1.14.0"
3
3
  end
@@ -86,6 +86,7 @@ module Inkcite
86
86
 
87
87
  # Initializing to prevent a ruby verbose warning.
88
88
  @footnotes = nil
89
+ @substitutions = nil
89
90
 
90
91
  end
91
92
 
@@ -338,7 +339,15 @@ module Inkcite
338
339
  # Returns the array of browser prefixes that need to be included in
339
340
  # CSS styles based on which version of the email this is.
340
341
  def prefixes
341
- [ '', '-webkit-' ]
342
+ _prefixes = [ '' ]
343
+
344
+ # In development mode, include all prefixes for maximum compatibility.
345
+ _prefixes += %w(-moz- -ms- -o-) if development?
346
+
347
+ # Always include webkit for targeting mobile devices.
348
+ _prefixes << '-webkit-'
349
+
350
+ _prefixes
342
351
  end
343
352
 
344
353
  def preview?
@@ -376,6 +385,9 @@ module Inkcite
376
385
  # <!-- commented out --> to ensure the email is as small as possible.
377
386
  source = Minifier.remove_comments(source, self)
378
387
 
388
+ # Perform global substitution of characters - e.g. ’ into ' if that's
389
+ substitution_map.each_pair { |orig, replace| source.gsub!(orig, replace) }
390
+
379
391
  # Protect against unsupported characters
380
392
  source = Renderer.fix_illegal_characters(source, self)
381
393
 
@@ -509,6 +521,32 @@ module Inkcite
509
521
  @subject ||= Renderer.render((self[:subject] || self[:title] || UNTITLED_EMAIL), self)
510
522
  end
511
523
 
524
+ # Returns a map where the strings are an original string
525
+ # and the value is what should replace it - used for
526
+ # automating common replacements - e.g. trademark symbols
527
+ # or apostrophes going in the wrong direction
528
+ def substitution_map
529
+
530
+ if @substitutions.nil?
531
+
532
+ # Initialize the substitutions map which will be populated
533
+ # if the local file exists.
534
+ @substitutions = {}
535
+
536
+ # Preload the array of footnotes if they exist
537
+ substitutions_tsv_file = @email.project_file(SUBSTITUTIONS_TSV_FILE)
538
+ if File.exist?(substitutions_tsv_file)
539
+ CSV.foreach(substitutions_tsv_file, { :col_sep => "\t" }) do |fn|
540
+ original = fn[0]
541
+ @substitutions[original] = fn[1].to_s unless original.blank?
542
+ end
543
+ end
544
+
545
+ end
546
+
547
+ @substitutions
548
+ end
549
+
512
550
  def tag_stack tag
513
551
  @tag_stack ||= Hash.new()
514
552
  @tag_stack[tag] ||= TagStack.new(tag, self)
@@ -623,6 +661,9 @@ module Inkcite
623
661
  # Tab-separated file containing footnote declarations.
624
662
  FOOTNOTES_TSV_FILE = 'footnotes.tsv'
625
663
 
664
+ # Tab-separated file containing global substitution values.
665
+ SUBSTITUTIONS_TSV_FILE = 'substitutions.tsv'
666
+
626
667
  def assert_in_browser msg
627
668
  raise msg if email? && !development?
628
669
  end
@@ -1,17 +1,20 @@
1
-
2
-
3
1
  describe Inkcite::Animation do
4
2
 
5
3
  before do
6
4
  @view = Inkcite::Email.new('test/project/').view(:development, :email)
5
+ @production_view = Inkcite::Email.new('test/project/').view(:production, :email)
7
6
  end
8
7
 
9
8
  it 'supports browser prefixing' do
10
- Inkcite::Renderer::Style.new(nil, @view, { :animation => 'video-frames 5s ease infinite' }).to_s.must_equal('-webkit-animation:video-frames 5s ease infinite;animation:video-frames 5s ease infinite')
9
+ Inkcite::Renderer::Style.new(nil, @view, { :animation => 'video-frames 5s ease infinite' }).to_s.must_equal('-moz-animation:video-frames 5s ease infinite;-ms-animation:video-frames 5s ease infinite;-o-animation:video-frames 5s ease infinite;-webkit-animation:video-frames 5s ease infinite;animation:video-frames 5s ease infinite')
10
+ end
11
+
12
+ it 'supports limited browser prefixing in production' do
13
+ Inkcite::Renderer::Style.new(nil, @production_view, { :animation => 'video-frames 5s ease infinite' }).to_s.must_equal('-webkit-animation:video-frames 5s ease infinite;animation:video-frames 5s ease infinite')
11
14
  end
12
15
 
13
16
  it 'supports browser prefixing specific items only' do
14
- Inkcite::Renderer::Style.new(nil, @view, { :left => '25%', :animation => 'video-frames 5s ease infinite' }).to_s.must_equal('-webkit-animation:video-frames 5s ease infinite;animation:video-frames 5s ease infinite;left:25%')
17
+ Inkcite::Renderer::Style.new(nil, @view, { :left => '25%', :animation => 'video-frames 5s ease infinite' }).to_s.must_equal('-moz-animation:video-frames 5s ease infinite;-ms-animation:video-frames 5s ease infinite;-o-animation:video-frames 5s ease infinite;-webkit-animation:video-frames 5s ease infinite;animation:video-frames 5s ease infinite;left:25%')
15
18
  end
16
19
 
17
20
  it 'can instantiate an animation keyframe' do
@@ -35,8 +38,38 @@ describe Inkcite::Animation do
35
38
  anim = Inkcite::Animation.new('snowflake7', @view)
36
39
  anim.add_keyframe 5, { :top => '-10px', :left => '22%', :transform => 'rotate(14deg)' }
37
40
  anim.add_keyframe 25, { :top => '100%', :left => '18%' }
38
- anim.to_keyframe_css.must_equal(%Q(@keyframes snowflake7 {\n5% { left:22%;top:-10px;transform:rotate(14deg) }\n25% { left:18%;top:100% }\n}\n@-webkit-keyframes snowflake7 {\n5% { -webkit-transform:rotate(14deg);left:22%;top:-10px }\n25% { left:18%;top:100% }\n}\n))
41
+ anim.to_keyframe_css.must_equal(%Q(@keyframes snowflake7 {\n5% { left:22%;top:-10px;transform:rotate(14deg) }\n25% { left:18%;top:100% }\n}\n@-moz-keyframes snowflake7 {\n5% { -moz-transform:rotate(14deg);left:22%;top:-10px }\n25% { left:18%;top:100% }\n}\n@-ms-keyframes snowflake7 {\n5% { -ms-transform:rotate(14deg);left:22%;top:-10px }\n25% { left:18%;top:100% }\n}\n@-o-keyframes snowflake7 {\n5% { -o-transform:rotate(14deg);left:22%;top:-10px }\n25% { left:18%;top:100% }\n}\n@-webkit-keyframes snowflake7 {\n5% { -webkit-transform:rotate(14deg);left:22%;top:-10px }\n25% { left:18%;top:100% }\n}\n))
42
+ end
43
+
44
+ it 'supports keyframe duration' do
45
+ keyframe = Inkcite::Animation::Keyframe.new(25, @view)
46
+ keyframe.duration = 15.9
47
+ keyframe[:top] = '-15%'
48
+ keyframe.to_css('').must_equal('25%, 40.9% { top:-15% }')
49
+ end
50
+
51
+ it 'reports when an animation is blank' do
52
+ anim = Inkcite::Animation.new('snowflake7', @view)
53
+ anim.blank?.must_equal(true)
54
+ anim.add_keyframe 25, { :top => '100%', :left => '18%' }
55
+ anim.blank?.must_equal(false)
39
56
  end
40
57
 
58
+ it 'supports composite animations' do
59
+
60
+ comp_anim = Inkcite::Animation::Composite.new()
61
+
62
+ explosion = Inkcite::Animation.new('explosion', @view)
63
+ explosion.duration = 8
64
+ explosion.timing_function = Inkcite::Animation::EASE_OUT
65
+ comp_anim << explosion
66
+
67
+ gravity = Inkcite::Animation.new('gravity', @view)
68
+ gravity.iteration_count = 4
69
+ comp_anim << gravity
70
+
71
+ comp_anim.to_s.must_equal(%q(8s ease-out infinite explosion, 1s linear 4 gravity))
72
+
73
+ end
41
74
 
42
75
  end
@@ -117,4 +117,34 @@ describe Inkcite::Renderer::Div do
117
117
  @view.media_query.find_by_klass('m1').declarations.must_match('width:78px !important')
118
118
  end
119
119
 
120
+ it 'supports mobile padding' do
121
+ Inkcite::Renderer.render('{div mobile-padding="15px 10px"}{/div}', @view).must_equal('<div class="m1"></div>')
122
+ @view.media_query.find_by_klass('m1').declarations.must_match('padding:15px 10px')
123
+ end
124
+
125
+ it 'supports directional mobile padding' do
126
+ Inkcite::Renderer.render('{div mobile-padding-top=15}{/div}', @view).must_equal('<div class="m1"></div>')
127
+ @view.media_query.find_by_klass('m1').declarations.must_match('padding-top:15px')
128
+ end
129
+
130
+ it 'supports directional mobile padding override' do
131
+ Inkcite::Renderer.render('{div mobile-padding=30px mobile-padding-top=15}{/div}', @view).must_equal('<div class="m1"></div>')
132
+ @view.media_query.find_by_klass('m1').declarations.must_match('padding:30px;padding-top:15px')
133
+ end
134
+
135
+ it 'supports mobile text alignment' do
136
+ Inkcite::Renderer.render('{div mobile-text-align="center"}{/div}', @view).must_equal('<div class="m1"></div>')
137
+ @view.media_query.find_by_klass('m1').declarations.must_match('text-align:center')
138
+ end
139
+
140
+ it 'supports mobile text alignment override' do
141
+ Inkcite::Renderer.render('{div text-align="right" mobile-text-align="center"}{/div}', @view).must_equal('<div class="m1" style="text-align:right"></div>')
142
+ @view.media_query.find_by_klass('m1').declarations.must_match('text-align:center !important')
143
+ end
144
+
145
+ it 'supports mobile display override' do
146
+ Inkcite::Renderer.render('{div mobile-display="block"}{/div}', @view).must_equal('<div class="m1"></div>')
147
+ @view.media_query.find_by_klass('m1').declarations.must_match('display:block')
148
+ end
149
+
120
150
  end
@@ -17,6 +17,16 @@ describe Inkcite::Renderer::Td do
17
17
  @view.media_query.find_by_klass('m1').to_css.must_equal('td.m1 { padding:15px }')
18
18
  end
19
19
 
20
+ it 'supports directional mobile padding' do
21
+ Inkcite::Renderer.render('{td mobile-padding-top=15}', @view).must_equal('<td class="m1">')
22
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td.m1 { padding-top:15px }')
23
+ end
24
+
25
+ it 'supports mobile text alignment' do
26
+ Inkcite::Renderer.render('{td mobile-text-align="center"}{/td}', @view).must_equal('<td class="m1"></td>')
27
+ @view.media_query.find_by_klass('m1').declarations.must_match('text-align:center')
28
+ end
29
+
20
30
  it 'reuses a style that matches mobile padding' do
21
31
  Inkcite::Renderer.render('{table mobile-padding=15}{td}{table mobile-padding=15}{td}', @view).must_equal('<table border=0 cellpadding=0 cellspacing=0><tr><td class="m1"><table border=0 cellpadding=0 cellspacing=0><tr><td class="m1">')
22
32
  @view.media_query.find_by_klass('m1').to_css.must_equal('td.m1 { padding:15px }')
@@ -148,4 +158,19 @@ describe Inkcite::Renderer::Td do
148
158
  Inkcite::Renderer.render('{td nowrap}', @view).must_equal('<td nowrap>')
149
159
  end
150
160
 
161
+ it 'supports mobile width override' do
162
+ Inkcite::Renderer.render('{td width=30 mobile-width=15}', @view).must_equal('<td class="m1" width=30>')
163
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td.m1 { width:15px }')
164
+ end
165
+
166
+ it 'supports mobile height override' do
167
+ Inkcite::Renderer.render('{td height=30 mobile-height=15}', @view).must_equal('<td class="m1" height=30>')
168
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td.m1 { height:15px }')
169
+ end
170
+
171
+ it 'supports multiple mobile override attributes' do
172
+ Inkcite::Renderer.render('{td width=15 border-left="1px dotted #cccccc" mobile-display="block" mobile-width="100%" mobile-border-left="none" mobile-border-top="1px dotted #ccc"}', @view).must_equal('<td class="m1" style="border-left:1px dotted #cccccc" width=15>')
173
+ @view.media_query.find_by_klass('m1').to_css.must_equal('td.m1 { border-left:none !important;border-top:1px dotted #ccc;display:block;width:100% }')
174
+ end
175
+
151
176
  end
@@ -6,7 +6,7 @@ describe Inkcite::Renderer::VideoPreview do
6
6
 
7
7
  it 'renders an animated video preview' do
8
8
  Inkcite::Renderer.render('{video-preview id="ripcurl" href="https://www.campaignmonitor.com/customers/ripcurl" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame%1.jpg" width=600 height=337 frames=3 bgcolor="#5b5f66" gradient="#1d1f21"}', @view).must_equal(%Q(<!--[if !mso]><!-- -->\n<a class="video" href="https://www.campaignmonitor.com/customers/ripcurl" style="background-color:#5b5f66;background-image:radial-gradient(circle at center, #5b5f66, #1d1f21);color:#0099cc;display:block;text-decoration:none" target=_blank>\n<table border=0 cellpadding=0 cellspacing=0 style="-webkit-animation:15s ease infinite video1-frames;animation:15s ease infinite video1-frames;background:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) 0% 0% / cover no-repeat" width=100%><tr style="transition:all .5s cubic-bezier(0.075, 0.82, 0.165, 1)">\n<td width=25%><img alt="" border=0 src=images/vp-150x337.png style="display:block;height:auto;opacity:0;visibility:hidden" width=100%></td>\n<td align=center valign=middle width=50%>\n<div class="play-button" style="background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.1)); border: 4px solid white; border-radius: 50%; box-shadow: 0 1px 2px rgba(0,0,0,0.3), inset 0 1px 2px rgba(0,0,0,0.3); height: 34px; margin: 0 auto; padding: 18px 16px 18px 24px; transition: transform .5s cubic-bezier(0.075, 0.82, 0.165, 1); width: 34px;">\n<div style="border-color: transparent transparent transparent white; border-style: solid; border-width: 17px 0 17px 30px; display: block; font-size: 0; height: 0; Margin: 0 auto; width: 0;">&nbsp;</div>\n</div>\n</td>\n<td width=25%>&nbsp;</td>\n</tr></table>\n</a>\n<div style="background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg),url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg),url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg);display:none"></div>\n<!--<![endif]-->\n<!--[if mso]>\n<v:group xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" coordsize="600,337" coordorigin="0,0" href="https://www.campaignmonitor.com/customers/ripcurl" style="width:600px;height:337px;">\n<v:rect fill="t" stroked="f" style="position:absolute;width:600;height:337;"><v:fill src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg" type="frame"/></v:rect>\n<v:oval fill="t" strokecolor="white" strokeweight="4px" style="position:absolute;left:261;top:129;width:78;height:78"><v:fill color="black" opacity="30%"/></v:oval>\n<v:shape coordsize="24,32" path="m,l,32,24,16,xe" fillcolor="white" stroked="f" style="position:absolute;left:285;top:153;width:30;height:30;"/>\n</v:group>\n<![endif]-->))
9
- @view.styles.first.must_equal(%Q(.video:hover .play-button {\n transform: scale(1.1);\n}\n.video:hover tr {\n background-color: rgba(255, 255, 255, .2);\n}\n@keyframes video1-frames {\n0% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n22% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n33% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n55% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n66% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n88% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n100% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n}\n@-webkit-keyframes video1-frames {\n0% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n22% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n33% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n55% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n66% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n88% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n100% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n}\n))
9
+ @view.styles.first.must_equal(%Q(.video:hover .play-button {\n transform: scale(1.1);\n}\n.video:hover tr {\n background-color: rgba(255, 255, 255, .2);\n}\n@keyframes video1-frames {\n0% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n22% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n33% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n55% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n66% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n88% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n100% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n}\n@-moz-keyframes video1-frames {\n0% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n22% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n33% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n55% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n66% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n88% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n100% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n}\n@-ms-keyframes video1-frames {\n0% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n22% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n33% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n55% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n66% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n88% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n100% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n}\n@-o-keyframes video1-frames {\n0% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n22% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n33% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n55% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n66% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n88% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n100% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n}\n@-webkit-keyframes video1-frames {\n0% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n22% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n33% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n55% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame2.jpg) }\n66% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n88% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame3.jpg) }\n100% { background-image:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/103009/frame1.jpg) }\n}\n))
10
10
 
11
11
  # Verify that the transparent spacer image was created and then
12
12
  # clean it up.
@@ -0,0 +1,11 @@
1
+ describe Inkcite::Util do
2
+
3
+ it 'can convert an HSL color to a hex color' do
4
+ Inkcite::Util.hsl_to_color(78, 78, 78).must_equal('#d8f39b')
5
+ end
6
+
7
+ it 'can convert an HSL color to a hex color' do
8
+ Inkcite::Util.hsl_to_color(128, 100, 50).must_equal('#00ff22')
9
+ end
10
+
11
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inkcite
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.0
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey D. Hoffman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-20 00:00:00.000000000 Z
11
+ date: 2017-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: kraken-io
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: listen
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -361,7 +375,6 @@ files:
361
375
  - bin/inkcite
362
376
  - inkcite.gemspec
363
377
  - lib/inkcite.rb
364
- - lib/inkcite/animation.rb
365
378
  - lib/inkcite/cli/base.rb
366
379
  - lib/inkcite/cli/build.rb
367
380
  - lib/inkcite/cli/init.rb
@@ -371,6 +384,11 @@ files:
371
384
  - lib/inkcite/cli/test.rb
372
385
  - lib/inkcite/cli/validate.rb
373
386
  - lib/inkcite/email.rb
387
+ - lib/inkcite/facade.rb
388
+ - lib/inkcite/facade/animation.rb
389
+ - lib/inkcite/facade/element.rb
390
+ - lib/inkcite/facade/keyframe.rb
391
+ - lib/inkcite/facade/style.rb
374
392
  - lib/inkcite/mailer.rb
375
393
  - lib/inkcite/minifier.rb
376
394
  - lib/inkcite/parser.rb
@@ -380,7 +398,7 @@ files:
380
398
  - lib/inkcite/renderer/button.rb
381
399
  - lib/inkcite/renderer/container_base.rb
382
400
  - lib/inkcite/renderer/div.rb
383
- - lib/inkcite/renderer/element.rb
401
+ - lib/inkcite/renderer/fireworks.rb
384
402
  - lib/inkcite/renderer/footnote.rb
385
403
  - lib/inkcite/renderer/google_analytics.rb
386
404
  - lib/inkcite/renderer/image.rb
@@ -405,7 +423,6 @@ files:
405
423
  - lib/inkcite/renderer/span.rb
406
424
  - lib/inkcite/renderer/sparkle.rb
407
425
  - lib/inkcite/renderer/special_effect.rb
408
- - lib/inkcite/renderer/style.rb
409
426
  - lib/inkcite/renderer/table.rb
410
427
  - lib/inkcite/renderer/table_base.rb
411
428
  - lib/inkcite/renderer/td.rb
@@ -445,6 +462,7 @@ files:
445
462
  - test/renderer/video_preview_spec.rb
446
463
  - test/renderer_spec.rb
447
464
  - test/test_helper.rb
465
+ - test/util_spec.rb
448
466
  - test/view_spec.rb
449
467
  homepage: https://github.com/inkceptional/inkcite
450
468
  licenses:
@@ -499,4 +517,5 @@ test_files:
499
517
  - test/renderer/video_preview_spec.rb
500
518
  - test/renderer_spec.rb
501
519
  - test/test_helper.rb
520
+ - test/util_spec.rb
502
521
  - test/view_spec.rb