inkcite 1.11.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -2
  3. data/assets/init/helpers.tsv +7 -0
  4. data/assets/social/facebook.png +0 -0
  5. data/assets/social/pintrest.png +0 -0
  6. data/assets/social/twitter.png +0 -0
  7. data/inkcite.gemspec +5 -4
  8. data/lib/inkcite.rb +17 -6
  9. data/lib/inkcite/animation.rb +135 -0
  10. data/lib/inkcite/cli/base.rb +5 -14
  11. data/lib/inkcite/cli/build.rb +3 -3
  12. data/lib/inkcite/cli/init.rb +3 -3
  13. data/lib/inkcite/cli/preview.rb +25 -1
  14. data/lib/inkcite/cli/server.rb +1 -1
  15. data/lib/inkcite/cli/validate.rb +2 -11
  16. data/lib/inkcite/email.rb +1 -1
  17. data/lib/inkcite/mailer.rb +5 -0
  18. data/lib/inkcite/minifier.rb +58 -11
  19. data/lib/inkcite/renderer.rb +10 -2
  20. data/lib/inkcite/renderer/base.rb +45 -3
  21. data/lib/inkcite/renderer/button.rb +16 -12
  22. data/lib/inkcite/renderer/container_base.rb +14 -4
  23. data/lib/inkcite/renderer/element.rb +13 -3
  24. data/lib/inkcite/renderer/image.rb +46 -19
  25. data/lib/inkcite/renderer/image_base.rb +3 -3
  26. data/lib/inkcite/renderer/in_browser.rb +1 -1
  27. data/lib/inkcite/renderer/link.rb +91 -59
  28. data/lib/inkcite/renderer/lorem.rb +9 -1
  29. data/lib/inkcite/renderer/mobile_image.rb +3 -11
  30. data/lib/inkcite/renderer/mobile_only.rb +48 -0
  31. data/lib/inkcite/renderer/outlook_background.rb +61 -13
  32. data/lib/inkcite/renderer/property.rb +1 -1
  33. data/lib/inkcite/renderer/responsive.rb +10 -8
  34. data/lib/inkcite/renderer/snow.rb +20 -10
  35. data/lib/inkcite/renderer/social.rb +128 -0
  36. data/lib/inkcite/renderer/table.rb +13 -1
  37. data/lib/inkcite/renderer/table_base.rb +3 -3
  38. data/lib/inkcite/renderer/td.rb +32 -7
  39. data/lib/inkcite/renderer/video_preview.rb +257 -0
  40. data/lib/inkcite/uploader.rb +3 -3
  41. data/lib/inkcite/util.rb +19 -5
  42. data/lib/inkcite/version.rb +1 -1
  43. data/lib/inkcite/view.rb +29 -18
  44. data/lib/inkcite/view/context.rb +30 -0
  45. data/test/animation_spec.rb +38 -0
  46. data/test/email_spec.rb +0 -4
  47. data/test/minifier_spec.rb +243 -4
  48. data/test/parser_spec.rb +0 -4
  49. data/test/project/helpers.tsv +3 -0
  50. data/test/renderer/button_spec.rb +15 -13
  51. data/test/renderer/div_spec.rb +13 -4
  52. data/test/renderer/element_spec.rb +1 -5
  53. data/test/renderer/footnote_spec.rb +0 -4
  54. data/test/renderer/image_spec.rb +27 -11
  55. data/test/renderer/link_spec.rb +14 -4
  56. data/test/renderer/lorem_spec.rb +0 -4
  57. data/test/renderer/mobile_image_spec.rb +3 -11
  58. data/test/renderer/mobile_only_spec.rb +21 -0
  59. data/test/renderer/mobile_style_spec.rb +1 -5
  60. data/test/renderer/outlook_background_spec.rb +61 -0
  61. data/test/renderer/redacted_spec.rb +0 -4
  62. data/test/renderer/social_spec.rb +53 -0
  63. data/test/renderer/span_spec.rb +0 -4
  64. data/test/renderer/table_spec.rb +8 -4
  65. data/test/renderer/td_spec.rb +0 -4
  66. data/test/renderer/video_preview_spec.rb +19 -0
  67. data/test/renderer_spec.rb +0 -4
  68. data/test/test_helper.rb +7 -0
  69. data/test/view_spec.rb +12 -4
  70. metadata +89 -56
@@ -17,6 +17,7 @@ require_relative 'renderer/link'
17
17
  require_relative 'renderer/litmus_analytics'
18
18
  require_relative 'renderer/lorem'
19
19
  require_relative 'renderer/mobile_image'
20
+ require_relative 'renderer/mobile_only'
20
21
  require_relative 'renderer/mobile_style'
21
22
  require_relative 'renderer/mobile_toggle'
22
23
  require_relative 'renderer/outlook_background'
@@ -25,9 +26,11 @@ require_relative 'renderer/preheader'
25
26
  require_relative 'renderer/property'
26
27
  require_relative 'renderer/redacted'
27
28
  require_relative 'renderer/snow'
29
+ require_relative 'renderer/social'
28
30
  require_relative 'renderer/span'
29
31
  require_relative 'renderer/table'
30
32
  require_relative 'renderer/td'
33
+ require_relative 'renderer/video_preview'
31
34
 
32
35
  module Inkcite
33
36
  module Renderer
@@ -116,7 +119,7 @@ module Inkcite
116
119
  end
117
120
 
118
121
  def self.quote val
119
- "\"#{val}\""
122
+ %Q("#{val}")
120
123
  end
121
124
 
122
125
  def self.render str, context
@@ -166,6 +169,7 @@ module Inkcite
166
169
  :a => Link.new,
167
170
  :button => Button.new,
168
171
  :div => Div.new,
172
+ :facebook => Social::Facebook.new,
169
173
  :footnote => Footnote.new,
170
174
  :footnotes => Footnotes.new,
171
175
  :google => GoogleAnalytics.new,
@@ -176,15 +180,19 @@ module Inkcite
176
180
  :litmus => LitmusAnalytics.new,
177
181
  :lorem => Lorem.new,
178
182
  :'mobile-img' => MobileImage.new,
183
+ :'mobile-only' => MobileOnly.new,
179
184
  :'mobile-style' => MobileStyle.new,
180
185
  :'mobile-toggle-on' => MobileToggleOn.new,
181
186
  :'outlook-bg' => OutlookBackground.new,
187
+ :pintrest => Social::Pintrest.new,
182
188
  :preheader => Preheader.new,
183
189
  :redacted => Redacted.new,
184
190
  :snow => Snow.new,
185
191
  :span => Span.new,
186
192
  :table => Table.new,
187
- :td => Td.new
193
+ :td => Td.new,
194
+ :twitter => Social::Twitter.new,
195
+ :'video-preview' => VideoPreview.new
188
196
  }
189
197
 
190
198
  end
@@ -4,6 +4,7 @@ module Inkcite
4
4
 
5
5
  # Constants for style and property names with dashes in them.
6
6
  BACKGROUND_COLOR = :'background-color'
7
+ BACKGROUND_GRADIENT = :'background-gradient'
7
8
  BACKGROUND_IMAGE = :'background-image'
8
9
  BACKGROUND_REPEAT = :'background-repeat'
9
10
  BACKGROUND_POSITION = :'background-position'
@@ -33,6 +34,7 @@ module Inkcite
33
34
  TEXT_SHADOW_BLUR = :'shadow-blur'
34
35
  TEXT_SHADOW_OFFSET = :'shadow-offset'
35
36
  VERTICAL_ALIGN = :'vertical-align'
37
+ WEBKIT_ANIMATION = :'-webkit-animation'
36
38
  WHITE_SPACE = :'white-space'
37
39
 
38
40
  # CSS Margins
@@ -51,6 +53,9 @@ module Inkcite
51
53
  # Zero-width space character
52
54
  ZERO_WIDTH_SPACE = '​'
53
55
 
56
+ # Zero-width non-breaking character
57
+ ZERO_WIDTH_NON_BREAKING_SPACE = ''
58
+
54
59
  def render tag, opt, ctx
55
60
  raise "Not implemented: #{tag} #{opts}"
56
61
  end
@@ -75,8 +80,9 @@ module Inkcite
75
80
  val
76
81
  end
77
82
 
78
- def detect_bgcolor opt
79
- detect(opt[:bgcolor], opt[BACKGROUND_COLOR])
83
+ def detect_bgcolor opt, default=nil
84
+ bgcolor = detect(opt[:bgcolor], opt[BACKGROUND_COLOR], default)
85
+ none?(bgcolor) ? nil : hex(bgcolor)
80
86
  end
81
87
 
82
88
  # Convenience pass-thru to Renderer's static helper method.
@@ -92,13 +98,45 @@ module Inkcite
92
98
  val.blank? || val == NONE
93
99
  end
94
100
 
101
+ def mix_animation element, opt, ctx
102
+
103
+ animation = opt[:animation]
104
+ unless none?(animation)
105
+ element.style[:animation] = animation
106
+ element.style[WEBKIT_ANIMATION] = animation
107
+ end
108
+ end
109
+
95
110
  # Sets the element's in-line bgcolor style if it has been defined
96
111
  # in the provided options.
97
112
  def mix_background element, opt, ctx
98
113
 
99
114
  # Background color of the image, if populated.
100
115
  bgcolor = detect_bgcolor(opt)
101
- element.style[BACKGROUND_COLOR] = hex(bgcolor) unless none?(bgcolor)
116
+
117
+ # Set the background color if the element has one.
118
+ element.style[BACKGROUND_COLOR] = bgcolor if bgcolor
119
+
120
+ # Background gradient support
121
+ bggradient = detect(opt[:bggradient] || opt[BACKGROUND_GRADIENT])
122
+ unless none?(bggradient)
123
+
124
+ # As a shortcut a gradient can be specified simply by designating
125
+ # both a bgcolor and the gradient color - this will insert a radial
126
+ # gradient automatically.
127
+ if bggradient.start_with?('#')
128
+ bggradient = hex(bggradient)
129
+
130
+ # If a bgcolor is provided, the gradient goes bgcolor -> bggradient.
131
+ # Otherwise, it goes bggradient->darker(bggradient)
132
+ center_color = bgcolor ? bgcolor : bggradient
133
+ outer_color = bgcolor ? bggradient : Util.darken(bggradient)
134
+
135
+ bggradient = %Q(radial-gradient(circle at center, #{center_color}, #{outer_color}))
136
+ end
137
+
138
+ element.style[BACKGROUND_IMAGE] = bggradient
139
+ end
102
140
 
103
141
  end
104
142
 
@@ -201,6 +239,10 @@ module Inkcite
201
239
 
202
240
  end
203
241
 
242
+ def pct val
243
+ "#{val}%"
244
+ end
245
+
204
246
  def px val
205
247
  Renderer.px(val)
206
248
  end
@@ -12,7 +12,7 @@ module Inkcite
12
12
  end
13
13
 
14
14
  def bgcolor
15
- hex(@opt[:bgcolor] || @ctx[BUTTON_BGCOLOR] || @ctx[BUTTON_BACKGROUND_COLOR] || @ctx[Base::LINK_COLOR])
15
+ hex(@opt[:bgcolor] || @ctx[BUTTON_BGCOLOR] || @ctx[BUTTON_BACKGROUND_COLOR])
16
16
  end
17
17
 
18
18
  def border
@@ -28,8 +28,7 @@ module Inkcite
28
28
  end
29
29
 
30
30
  def bevel_color
31
- bc = @opt[BEVEL_COLOR] || @ctx[BUTTON_BEVEL_COLOR]
32
- !bc.blank?? hex(bc) : text_shadow
31
+ @opt[BEVEL_COLOR] || @ctx[BUTTON_BEVEL_COLOR]
33
32
  end
34
33
 
35
34
  def border_radius
@@ -37,7 +36,7 @@ module Inkcite
37
36
  end
38
37
 
39
38
  def color
40
- hex(@opt[:color] || @ctx[BUTTON_COLOR] || Util::contrasting_text_color(bgcolor))
39
+ hex(@opt[:color] || @ctx[BUTTON_COLOR])
41
40
  end
42
41
 
43
42
  def float
@@ -60,6 +59,10 @@ module Inkcite
60
59
  (@opt[:height] || @ctx[BUTTON_HEIGHT]).to_i
61
60
  end
62
61
 
62
+ def letter_spacing
63
+ @opt[Base::LETTER_SPACING] || @ctx[BUTTON_LETTER_SPACING]
64
+ end
65
+
63
66
  def line_height
64
67
  @opt[Base::LINE_HEIGHT] || @ctx[BUTTON_LINE_HEIGHT]
65
68
  end
@@ -73,11 +76,7 @@ module Inkcite
73
76
  end
74
77
 
75
78
  def text_shadow
76
- ts = @opt[Base::TEXT_SHADOW] || @ctx[BUTTON_TEXT_SHADOW]
77
- unless ts
78
- ts = Util::brightness_value(bgcolor) > 382.5 ? Util::lighten(bgcolor, 0.25) : Util::darken(bgcolor)
79
- end
80
- hex(ts)
79
+ hex(@opt[Base::TEXT_SHADOW] || @ctx[BUTTON_TEXT_SHADOW])
81
80
  end
82
81
 
83
82
  def width
@@ -100,6 +99,7 @@ module Inkcite
100
99
  BUTTON_FONT_SIZE = :'button-font-size'
101
100
  BUTTON_FONT_WEIGHT = :'button-font-weight'
102
101
  BUTTON_HEIGHT = :'button-height'
102
+ BUTTON_LETTER_SPACING = :'button-letter-spacing'
103
103
  BUTTON_LINE_HEIGHT = :'button-line-height'
104
104
  BUTTON_MARGIN_TOP = :'button-margin-top'
105
105
  BUTTON_PADDING = :'button-padding'
@@ -131,7 +131,8 @@ module Inkcite
131
131
  # Responsive button is just a highly styled table/td combination with optional
132
132
  # curved corners and a lower bevel (border).
133
133
  bgcolor = cfg.bgcolor
134
- html << "{table bgcolor=#{bgcolor}"
134
+ html << '{table'
135
+ html << %Q( bgcolor="#{bgcolor}") unless bgcolor.blank?
135
136
  html << " padding=#{cfg.padding}" if cfg.padding > 0
136
137
  html << %Q( border="#{cfg.border}") if cfg.border
137
138
  html << " border-radius=#{cfg.border_radius}" if cfg.border_radius > 0
@@ -139,7 +140,7 @@ module Inkcite
139
140
 
140
141
  # Need to separate borders that are collapsed by default - otherwise, the bevel
141
142
  # renders incorrectly.
142
- html << " border-collapse=separate" if cfg.border || cfg.bevel > 0
143
+ html << ' border-collapse=separate' if cfg.border || cfg.bevel > 0
143
144
 
144
145
  html << " margin-top=#{cfg.margin_top}" if cfg.margin_top > 0
145
146
  html << " width=#{cfg.width}" if cfg.width > 0
@@ -161,7 +162,10 @@ module Inkcite
161
162
 
162
163
  # Second, internal link for Outlook users that makes the inside of the button
163
164
  # clickable.
164
- html << "{a id=\"#{id}\" href=\"#{href}\" color=\"#{cfg.color}\"}"
165
+ html << %Q({a id="#{id}" href="#{href}" color="#{cfg.color}")
166
+ html << %Q( letter-spacing="#{cfg.letter_spacing}") unless cfg.letter_spacing.blank?
167
+ html << %q(})
168
+
165
169
 
166
170
  else
167
171
 
@@ -6,19 +6,17 @@ module Inkcite
6
6
 
7
7
  def mix_all element, opt, ctx
8
8
 
9
+ mix_animation element, opt, ctx
9
10
  mix_background element, opt, ctx
10
11
  mix_border element, opt, ctx
11
12
  mix_border_radius element, opt, ctx
12
13
  mix_font element, opt, ctx
14
+ mix_text_align element, opt, ctx
13
15
 
14
16
  # Supports both integers and mixed padding (e.g. 10px 20px)
15
17
  padding = opt[:padding]
16
18
  element.style[:padding] = px(padding) unless none?(padding)
17
19
 
18
- # Text alignment - left, right, center.
19
- align = opt[:align]
20
- element.style[TEXT_ALIGN] = align unless none?(align)
21
-
22
20
  # Vertical alignment - top, middle, bottom.
23
21
  valign = opt[:valign]
24
22
  element.style[VERTICAL_ALIGN] = valign unless none?(valign)
@@ -26,11 +24,23 @@ module Inkcite
26
24
  display = opt[:display]
27
25
  element.style[:display] = display unless display.blank?
28
26
 
27
+ # If boolean 'nowrap' attribute is present, apply the 'white-space: nowrap'
28
+ # style to the element.
29
+ element.style[WHITE_SPACE] = :nowrap if opt[:nowrap]
30
+
29
31
  mix_responsive element, opt, ctx
30
32
 
31
33
  element.to_s
32
34
  end
33
35
 
36
+ # Text alignment - left, right, center.
37
+ def mix_text_align element, opt, ctx
38
+
39
+ align = opt[:align]
40
+ element.style[TEXT_ALIGN] = align unless none?(align)
41
+
42
+ end
43
+
34
44
  end
35
45
  end
36
46
  end
@@ -10,6 +10,10 @@ module Inkcite
10
10
  @tag = tag
11
11
  @att = att
12
12
 
13
+ # Initializing @classes to avoid a Ruby warning that it hasn't been
14
+ # declared when it is lazy-initialized in the classes() method.
15
+ @classes = nil
16
+
13
17
  # True if the tag self-closes as in "<img .../>"
14
18
  @self_close = att.delete(:self_close) == true
15
19
 
@@ -67,7 +71,13 @@ module Inkcite
67
71
  @styles
68
72
  end
69
73
 
70
- def to_s
74
+ # Generates a Helper tag rather than a string tag - e.g. {img src=test.png}
75
+ # rather than <img src=test.png>
76
+ def to_helper
77
+ to_s('{', '}')
78
+ end
79
+
80
+ def to_s open='<', close='>'
71
81
 
72
82
  # Convert the style hash into CSS style attribute.
73
83
  @att[:style] = Renderer.quote(Renderer.render_styles(@styles)) unless @styles.blank?
@@ -75,7 +85,7 @@ module Inkcite
75
85
  # Convert the list of CSS classes assigned to this element into an attribute
76
86
  self[:class] = Renderer.quote(@classes.to_a.sort.join(' ')) unless @classes.blank?
77
87
 
78
- html = '<'
88
+ html = open
79
89
  html << @tag
80
90
 
81
91
  unless @att.empty?
@@ -84,7 +94,7 @@ module Inkcite
84
94
  end
85
95
 
86
96
  html << ' /' if self_close?
87
- html << '>'
97
+ html << close
88
98
 
89
99
  html
90
100
  end
@@ -9,10 +9,6 @@ module Inkcite
9
9
  # Ensure that height and width are defined in the image's attributes.
10
10
  mix_dimensions img, opt, ctx
11
11
 
12
- # Get the fully-qualified URL to the image or placeholder image if it's
13
- # missing from the images directory.
14
- img[:src] = image_url(opt[:src], opt, ctx)
15
-
16
12
  mix_background img, opt, ctx
17
13
  mix_border img, opt, ctx
18
14
 
@@ -23,10 +19,18 @@ module Inkcite
23
19
  if alt
24
20
 
25
21
  # Allow "\n" to be placed within alt text and converted into a line
26
- # break for convenience. Need to add an extra space for the email
27
- # clients (ahem, Gmail, cough) that don't support alt text with
28
- # line breaks.
29
- alt.gsub!('\n', "\n ")
22
+ # break for convenience.
23
+ alt.gsub!('\n', "\n")
24
+
25
+ # Need to add an extra space for the email clients (ahem, Gmail,
26
+ # cough) that don't support alt text with line breaks.
27
+ alt.gsub!("\n", " \n")
28
+
29
+ # Remove all HTML from the alt text. Ran into a situation where a
30
+ # custom Helper was applying styled text as image alt text. Since
31
+ # HTML isn't allowed within alt text, as a convenience we'll just
32
+ # delete said markup.
33
+ alt.gsub!(/<[^>]*>/, '')
30
34
 
31
35
  # Ensure that the alt-tag has quotes around it.
32
36
  img[:alt] = quote(alt)
@@ -38,9 +42,6 @@ module Inkcite
38
42
  # Copy the text to the title attribute if enabled for this issue
39
43
  img[:title] = img[:alt] if ctx.is_enabled?(COPY_ALT_TO_TITLE)
40
44
 
41
- # All images with alt text inherit small font unless otherwise specified.
42
- opt[:font] ||= 'small'
43
-
44
45
  mix_font img, opt, ctx
45
46
 
46
47
  text_align = opt[TEXT_ALIGN]
@@ -71,6 +72,29 @@ module Inkcite
71
72
  valign = opt[:valign] || ('middle' if inline)
72
73
  img.style[VERTICAL_ALIGN] = valign unless valign.blank?
73
74
 
75
+ html = ''
76
+
77
+ # Check to see if an outlook-specific image source has been
78
+ # specified - typically used when there is an animated gif as
79
+ # the main source but a static image for Outlook clients.
80
+ outlook_src = opt[OUTLOOK_SRC]
81
+ unless outlook_src.blank?
82
+
83
+ # Initially set the image's URL to the outlook-specific image.
84
+ img[:src] = image_url(outlook_src, opt, ctx)
85
+
86
+ # Wrap the image in the outlook-specific conditionals.
87
+ html << '<!--[if mso]>'
88
+ html << img.to_s
89
+ html << '<![endif]-->'
90
+ html << '<!--[if !mso]><!-- -->'
91
+
92
+ end
93
+
94
+ # Get the fully-qualified URL to the image or placeholder image if it's
95
+ # missing from the images directory.
96
+ img[:src] = image_url(opt[:src], opt, ctx)
97
+
74
98
  mobile_src = opt[:'mobile-src']
75
99
  unless mobile_src.blank?
76
100
 
@@ -79,7 +103,7 @@ module Inkcite
79
103
  klass = klass_name(mobile_src, ctx)
80
104
 
81
105
  # Fully-qualify the image URL.
82
- mobile_src = image_url(mobile_src, opt, ctx)
106
+ mobile_src = image_url(mobile_src, opt, ctx, false)
83
107
 
84
108
  # Add a responsive rule that replaces the image with a different source
85
109
  # with the same dimensions. Warning, this isn't supported on earlier
@@ -106,17 +130,16 @@ module Inkcite
106
130
  # maintain aspect ratio if present.
107
131
  img[:height] = nil
108
132
 
109
- else
110
-
111
- # Check to see if this image is inside of a mobile-image declaration.
112
- # If so, the image defaults to hide on mobile.
113
- mobile = HIDE if mobile.blank? && !ctx.parent_opts(:mobile_image).blank?
114
-
115
133
  end
116
134
 
117
135
  mix_responsive img, opt, ctx, mobile
118
136
 
119
- img.to_s
137
+ html << img.to_s
138
+
139
+ # Conclude the outlook-specific conditional if opened.
140
+ html << '<!--<![endif]-->' unless outlook_src.blank?
141
+
142
+ html
120
143
  end
121
144
 
122
145
  private
@@ -125,6 +148,10 @@ module Inkcite
125
148
  # be copied to the title field.
126
149
  COPY_ALT_TO_TITLE = :'copy-alt-to-title'
127
150
 
151
+ # Name of the property that allows an outlook-specific src to be specified
152
+ # for an image.
153
+ OUTLOOK_SRC = :'outlook-src'
154
+
128
155
  end
129
156
  end
130
157
  end
@@ -12,7 +12,7 @@ module Inkcite
12
12
  # For the given image source URL provided, returns either the fully-qualfied
13
13
  # path to the image (via View's image_url method) or returns a placeholder
14
14
  # if the image is missing.
15
- def image_url _src, opt, ctx
15
+ def image_url _src, opt, ctx, assert_dimensions=true
16
16
 
17
17
  src = _src
18
18
 
@@ -71,7 +71,7 @@ module Inkcite
71
71
  # Replace the missing image with an imgix.net-powered placeholder using
72
72
  # the query parameters assembled above.
73
73
  # e.g. https://placeholdit.imgix.net/~text?txtsize=18&txt=left.jpg%0A%28155%C3%97155%29&w=155&h=155&fm=jpg&txttrack=0
74
- src = "//placeholdit.imgix.net/~text?#{query.to_query}"
74
+ src = "http://placeholdit.imgix.net/~text?#{query.to_query}"
75
75
 
76
76
  end
77
77
 
@@ -79,7 +79,7 @@ module Inkcite
79
79
 
80
80
  # Don't let an image go into production without dimensions. Using the original
81
81
  # src so that we don't display the verbose qualified URL to the developer.
82
- ctx.error('Missing image dimensions', { :src => _src }) if missing_dimensions
82
+ ctx.error('Missing image dimensions', { :src => _src }) if missing_dimensions && assert_dimensions
83
83
 
84
84
  quote(src)
85
85
  end