inkcite 1.14.0 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/assets/init/config.yml +2 -0
- data/assets/social/facebook.png +0 -0
- data/assets/social/instagram.png +0 -0
- data/assets/social/pintrest.png +0 -0
- data/assets/social/twitter.png +0 -0
- data/inkcite.gemspec +2 -2
- data/lib/inkcite.rb +1 -0
- data/lib/inkcite/cli/base.rb +32 -6
- data/lib/inkcite/cli/preview.rb +24 -28
- data/lib/inkcite/cli/server.rb +22 -19
- data/lib/inkcite/cli/test.rb +1 -1
- data/lib/inkcite/email.rb +8 -4
- data/lib/inkcite/facade/animation.rb +4 -0
- data/lib/inkcite/facade/keyframe.rb +26 -3
- data/lib/inkcite/image/base.rb +38 -0
- data/lib/inkcite/image/guetzli_minifier.rb +62 -0
- data/lib/inkcite/image/image_minifier.rb +143 -0
- data/lib/inkcite/image/image_optim_minifier.rb +90 -0
- data/lib/inkcite/image/mozjpeg_minifier.rb +92 -0
- data/lib/inkcite/mailer.rb +201 -112
- data/lib/inkcite/minifier.rb +2 -146
- data/lib/inkcite/post_processor.rb +13 -0
- data/lib/inkcite/renderer.rb +19 -0
- data/lib/inkcite/renderer/background.rb +53 -14
- data/lib/inkcite/renderer/base.rb +29 -15
- data/lib/inkcite/renderer/button.rb +1 -1
- data/lib/inkcite/renderer/carousel.rb +245 -0
- data/lib/inkcite/renderer/container_base.rb +10 -0
- data/lib/inkcite/renderer/div.rb +1 -3
- data/lib/inkcite/renderer/fireworks.rb +54 -40
- data/lib/inkcite/renderer/footnote.rb +22 -2
- data/lib/inkcite/renderer/image.rb +11 -0
- data/lib/inkcite/renderer/image_base.rb +3 -6
- data/lib/inkcite/renderer/in_browser.rb +4 -0
- data/lib/inkcite/renderer/link.rb +39 -12
- data/lib/inkcite/renderer/mobile_image.rb +1 -1
- data/lib/inkcite/renderer/responsive.rb +9 -1
- data/lib/inkcite/renderer/social.rb +31 -3
- data/lib/inkcite/renderer/special_effect.rb +22 -13
- data/lib/inkcite/renderer/sup.rb +32 -0
- data/lib/inkcite/renderer/table_base.rb +3 -0
- data/lib/inkcite/renderer/topic.rb +76 -0
- data/lib/inkcite/renderer/trademark.rb +47 -0
- data/lib/inkcite/renderer/video_preview.rb +3 -2
- data/lib/inkcite/uploader.rb +2 -3
- data/lib/inkcite/util.rb +51 -0
- data/lib/inkcite/version.rb +1 -1
- data/lib/inkcite/view.rb +140 -54
- data/lib/inkcite/view/context.rb +1 -31
- data/lib/inkcite/view/media_query.rb +6 -0
- data/test/animation_spec.rb +7 -0
- data/test/parser_spec.rb +1 -1
- data/test/renderer/background_spec.rb +16 -12
- data/test/renderer/div_spec.rb +11 -0
- data/test/renderer/footnote_spec.rb +5 -1
- data/test/renderer/image_spec.rb +51 -28
- data/test/renderer/link_spec.rb +20 -8
- data/test/renderer/lorem_spec.rb +2 -2
- data/test/renderer/mobile_image_spec.rb +6 -0
- data/test/renderer/mobile_style_spec.rb +3 -3
- data/test/renderer/redacted_spec.rb +2 -2
- data/test/renderer/social_spec.rb +6 -6
- data/test/renderer/table_spec.rb +4 -0
- data/test/renderer/topic_spec.rb +28 -0
- data/test/renderer/trademark_spec.rb +40 -0
- data/test/renderer/video_preview_spec.rb +1 -1
- data/test/test_helper.rb +14 -0
- data/test/view_spec.rb +4 -0
- metadata +26 -12
- data/assets/init/image_optim.yml +0 -37
data/lib/inkcite/minifier.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
+
require_relative 'image/image_minifier'
|
2
|
+
|
1
3
|
module Inkcite
|
2
4
|
class Minifier
|
3
5
|
|
4
|
-
# Directory of optimized images
|
5
|
-
IMAGE_CACHE = 'images-optim'
|
6
|
-
|
7
6
|
# Maximum line length for CSS and HTML - lines exceeding this length cause
|
8
7
|
# problems in certain email clients.
|
9
8
|
MAXIMUM_LINE_LENGTH = 800
|
@@ -124,81 +123,6 @@ module Inkcite
|
|
124
123
|
|
125
124
|
end
|
126
125
|
|
127
|
-
def self.image email, img_name, force=false
|
128
|
-
|
129
|
-
# Original, unoptimized source image
|
130
|
-
source_img = File.join(email.image_dir, img_name)
|
131
|
-
|
132
|
-
# Cached, optimized path for this image.
|
133
|
-
cache_path = email.project_file(IMAGE_CACHE)
|
134
|
-
cached_img = File.join(cache_path, File.basename(img_name))
|
135
|
-
|
136
|
-
# Full path to the local project's kraken config if it exists
|
137
|
-
kraken_config_path = email.project_file(KRAKEN_CONFIG_YML)
|
138
|
-
|
139
|
-
# This is the array of config files that will be searched to
|
140
|
-
# determine which algorithm to use to compress the images.
|
141
|
-
config_paths = [
|
142
|
-
kraken_config_path,
|
143
|
-
email.project_file(IMAGE_OPTIM_CONFIG_YML),
|
144
|
-
File.join(Inkcite.asset_path, 'init', IMAGE_OPTIM_CONFIG_YML)
|
145
|
-
]
|
146
|
-
|
147
|
-
# Grab the first file that exists for this project.
|
148
|
-
config_path = config_paths.detect { |p| File.exist?(p) }
|
149
|
-
|
150
|
-
unless force
|
151
|
-
|
152
|
-
# Get the last-modified date of the image optimization config
|
153
|
-
# file - if that file is newer than the image, re-optimization
|
154
|
-
# is necessary because the settings have changed.
|
155
|
-
config_last_modified = Util.last_modified(config_path)
|
156
|
-
|
157
|
-
# Get the last-modified date of the actual image. If the source
|
158
|
-
# image is newer than the cached version, we'll need to run it
|
159
|
-
# through optimization again, too.
|
160
|
-
cache_last_modified = Util.last_modified(cached_img)
|
161
|
-
source_last_modified = Util.last_modified(source_img)
|
162
|
-
|
163
|
-
# Nothing to do unless the image in the cache is older than the
|
164
|
-
# source or the config file.
|
165
|
-
return unless config_last_modified > cache_last_modified || source_last_modified > cache_last_modified
|
166
|
-
|
167
|
-
end
|
168
|
-
|
169
|
-
# Make sure the image cache directory exists
|
170
|
-
FileUtils.mkpath(cache_path)
|
171
|
-
|
172
|
-
# Read the image compression configuration settings
|
173
|
-
config = Util::read_yml(config_path, :fail_if_not_exists => false)
|
174
|
-
|
175
|
-
if config_path == kraken_config_path
|
176
|
-
minify_with_kraken_io email, config, source_img, cached_img
|
177
|
-
|
178
|
-
else
|
179
|
-
|
180
|
-
# Default image optimization uses built-in ImageOptim
|
181
|
-
minify_with_image_optim email, config, source_img, cached_img
|
182
|
-
|
183
|
-
end
|
184
|
-
|
185
|
-
original_size = File.size(source_img)
|
186
|
-
compressed_size = File.size(cached_img)
|
187
|
-
percent_compressed = ((1.0 - (compressed_size / original_size.to_f)) * 100).round(1)
|
188
|
-
puts "Compressed #{img_name} #{percent_compressed}%"
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
|
-
def self.images email, force=false
|
193
|
-
|
194
|
-
images_path = email.image_dir
|
195
|
-
|
196
|
-
# Iterate through all of the images in the project and optimize them
|
197
|
-
# if necessary.
|
198
|
-
Dir.glob(File.join(images_path, '*.*')).each { |img| self.image(email, File.basename(img), force) }
|
199
|
-
|
200
|
-
end
|
201
|
-
|
202
126
|
def self.js code, ctx
|
203
127
|
minify?(ctx) ? js_compressor(ctx).compress(code) : code
|
204
128
|
end
|
@@ -209,74 +133,6 @@ module Inkcite
|
|
209
133
|
|
210
134
|
private
|
211
135
|
|
212
|
-
def self.minify_with_image_optim email, config, source_img, cached_img
|
213
|
-
|
214
|
-
# Copy the image into the destination directory and then use Image Optim
|
215
|
-
# to optimize it in place.
|
216
|
-
FileUtils.cp(source_img, cached_img)
|
217
|
-
ImageOptim.new(config).optimize_image!(cached_img)
|
218
|
-
|
219
|
-
end
|
220
|
-
|
221
|
-
def self.minify_with_kraken_io email, config, source_img, cached_img
|
222
|
-
|
223
|
-
require 'kraken-io'
|
224
|
-
require 'open-uri'
|
225
|
-
|
226
|
-
# Initialize the Kraken API using the API key and secret defined in the
|
227
|
-
# config.yml file.
|
228
|
-
kraken = Kraken::API.new(
|
229
|
-
:api_key => config[:api_key],
|
230
|
-
:api_secret => config[:api_secret]
|
231
|
-
)
|
232
|
-
|
233
|
-
# As you might expect, Outlook doesn't support webp so it needs to be
|
234
|
-
# disabled by default. Otherwise, Kraken always compresses with webp.
|
235
|
-
kraken_opts = { :webp => false }
|
236
|
-
|
237
|
-
# Get the file format (e.g. gif) of the file being optimized.
|
238
|
-
source_fmt = File.extname(source_img).delete('.')
|
239
|
-
|
240
|
-
# True if the configuration file does not specifically exclude
|
241
|
-
# this format from being processed.
|
242
|
-
compress_this_fmt = config[source_fmt.to_sym] != false
|
243
|
-
|
244
|
-
# Typically, we're going to want lossy compression to minify the file
|
245
|
-
# but if the user has put lossy: false specifically in their config
|
246
|
-
# file, we'll disable that feature in Kraken too. Defaults to true.
|
247
|
-
kraken_opts[:lossy] = compress_this_fmt
|
248
|
-
|
249
|
-
# Send the quality metric to Kraken only if specified. Per their
|
250
|
-
# documentation, Kraken will attempt to guess the best quality to
|
251
|
-
# use but in my experience it errs on the side of higher quality
|
252
|
-
# whereas setting a quality factor around 50 produces a good
|
253
|
-
# balance of image detail and file size.
|
254
|
-
if compress_this_fmt
|
255
|
-
quality = config[:quality].to_i
|
256
|
-
kraken_opts[:quality] = quality if quality > 0 and quality <= 100
|
257
|
-
end
|
258
|
-
|
259
|
-
# Upload the image to Kraken which blocks by default until the image
|
260
|
-
# has been optimized.
|
261
|
-
data = kraken.upload(source_img, kraken_opts)
|
262
|
-
if data.success
|
263
|
-
File.write(cached_img, open(data.kraked_url).read, { :mode => 'wb' })
|
264
|
-
else
|
265
|
-
puts "Failed to optimize #{img_name}: #{data.message}"
|
266
|
-
end
|
267
|
-
|
268
|
-
end
|
269
|
-
|
270
|
-
# Name of the Image Optim configuration yml file that can be
|
271
|
-
# put in the project directory to explicitly control the image
|
272
|
-
# optimization process.
|
273
|
-
IMAGE_OPTIM_CONFIG_YML = 'image_optim.yml'
|
274
|
-
|
275
|
-
# Name of the Kraken configuration yml that, when present in
|
276
|
-
# the project directory and populated with an API key and secret
|
277
|
-
# causes Kraken.io paid image optimization service to be used.
|
278
|
-
KRAKEN_CONFIG_YML = 'kraken.yml'
|
279
|
-
|
280
136
|
NEW_LINE = "\n"
|
281
137
|
|
282
138
|
# Used to match inline styles that will be compressed when minifying
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Inkcite
|
2
|
+
module PostProcessor
|
3
|
+
|
4
|
+
def post_process html, ctx
|
5
|
+
raise 'Extending class must implement process(html, ctx)'
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.run_all html, ctx
|
9
|
+
ctx.post_processors.inject(html) { |h, pp| pp.post_process(h, ctx) }
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
data/lib/inkcite/renderer.rb
CHANGED
@@ -7,6 +7,7 @@ require_relative 'renderer/table_base'
|
|
7
7
|
|
8
8
|
require_relative 'renderer/background'
|
9
9
|
require_relative 'renderer/button'
|
10
|
+
require_relative 'renderer/carousel'
|
10
11
|
require_relative 'renderer/div'
|
11
12
|
require_relative 'renderer/fireworks'
|
12
13
|
require_relative 'renderer/footnote'
|
@@ -30,8 +31,11 @@ require_relative 'renderer/snow'
|
|
30
31
|
require_relative 'renderer/social'
|
31
32
|
require_relative 'renderer/span'
|
32
33
|
require_relative 'renderer/sparkle'
|
34
|
+
require_relative 'renderer/sup'
|
33
35
|
require_relative 'renderer/table'
|
34
36
|
require_relative 'renderer/td'
|
37
|
+
require_relative 'renderer/topic'
|
38
|
+
require_relative 'renderer/trademark'
|
35
39
|
require_relative 'renderer/video_preview'
|
36
40
|
|
37
41
|
module Inkcite
|
@@ -64,6 +68,9 @@ module Inkcite
|
|
64
68
|
|
65
69
|
end
|
66
70
|
|
71
|
+
# Remove unicode line break characters
|
72
|
+
value.gsub!(/\u2028/, '')
|
73
|
+
|
67
74
|
value
|
68
75
|
end
|
69
76
|
|
@@ -128,6 +135,10 @@ module Inkcite
|
|
128
135
|
|
129
136
|
Parser.each(str) do |tag|
|
130
137
|
|
138
|
+
# Record to the context the most recent tag being processed in case
|
139
|
+
# there are errors associated with it.
|
140
|
+
context.last_rendered_markup = tag
|
141
|
+
|
131
142
|
# Split the string into the tag and it's attributes.
|
132
143
|
name, opts = tag.split(SPACE, 2)
|
133
144
|
|
@@ -171,6 +182,8 @@ module Inkcite
|
|
171
182
|
:a => Link.new,
|
172
183
|
:background => Background.new,
|
173
184
|
:button => Button.new,
|
185
|
+
:carousel => Carousel.new,
|
186
|
+
:'carousel-img' => Carousel::Image.new,
|
174
187
|
:div => Div.new,
|
175
188
|
:facebook => Social::Facebook.new,
|
176
189
|
:fireworks => Fireworks.new,
|
@@ -180,6 +193,7 @@ module Inkcite
|
|
180
193
|
:img => Image.new,
|
181
194
|
:'in-browser' => InBrowser.new,
|
182
195
|
:include => Partial.new,
|
196
|
+
:instagram => Social::Instagram.new,
|
183
197
|
:like => Like.new,
|
184
198
|
:litmus => LitmusAnalytics.new,
|
185
199
|
:lorem => Lorem.new,
|
@@ -189,12 +203,17 @@ module Inkcite
|
|
189
203
|
:'mobile-toggle-on' => MobileToggleOn.new,
|
190
204
|
:pintrest => Social::Pintrest.new,
|
191
205
|
:preheader => Preheader.new,
|
206
|
+
:r => Trademark.new('®'),
|
192
207
|
:redacted => Redacted.new,
|
193
208
|
:snow => Snow.new,
|
194
209
|
:span => Span.new,
|
195
210
|
:sparkle => Sparkle.new,
|
211
|
+
:sup => Sup.new,
|
196
212
|
:table => Table.new,
|
197
213
|
:td => Td.new,
|
214
|
+
:tm => Trademark.new('™'),
|
215
|
+
:'topic' => Topic.new,
|
216
|
+
:'topic-list' => TopicList.new,
|
198
217
|
:twitter => Social::Twitter.new,
|
199
218
|
:'video-preview' => VideoPreview.new
|
200
219
|
}
|
@@ -14,8 +14,19 @@ module Inkcite
|
|
14
14
|
|
15
15
|
html = ''
|
16
16
|
|
17
|
+
|
18
|
+
tag_stack = ctx.tag_stack(:background)
|
19
|
+
|
17
20
|
if tag == '/background'
|
18
21
|
|
22
|
+
opening = tag_stack.pop
|
23
|
+
padding = opening[:padding].to_i
|
24
|
+
|
25
|
+
html << vs(padding) if padding > 0
|
26
|
+
html << '{/td}'
|
27
|
+
html << hs(padding) if padding > 0
|
28
|
+
|
29
|
+
html << '{/table}'
|
19
30
|
html << '</div>'
|
20
31
|
|
21
32
|
# If VML is enabled, then close the textbox and rect that were created
|
@@ -32,6 +43,8 @@ module Inkcite
|
|
32
43
|
|
33
44
|
else
|
34
45
|
|
46
|
+
tag_stack << opt
|
47
|
+
|
35
48
|
# Primary background image
|
36
49
|
src = opt[:src]
|
37
50
|
|
@@ -45,7 +58,6 @@ module Inkcite
|
|
45
58
|
fill_width = width.nil? || width == 'fill' || width == '100%' || width.to_i <= 0
|
46
59
|
|
47
60
|
table = Element.new('table')
|
48
|
-
table[:height] = height if height > 0
|
49
61
|
table[:width] = (fill_width ? '100%' : width)
|
50
62
|
table[:background] = quote(src) unless none?(src)
|
51
63
|
|
@@ -55,7 +67,7 @@ module Inkcite
|
|
55
67
|
# might interfere with the display of the background (e.g. padding)
|
56
68
|
TABLE_PASSTHRU_OPS.each do |key|
|
57
69
|
val = opt[key]
|
58
|
-
table[key] = quote(val) unless
|
70
|
+
table[key] = quote(val) unless val.blank?
|
59
71
|
end
|
60
72
|
|
61
73
|
# Determine if a fallback background color has been defined.
|
@@ -66,13 +78,8 @@ module Inkcite
|
|
66
78
|
bggradient = detect_bggradient(opt)
|
67
79
|
table[:bggradient] = quote(bggradient) unless none?(bggradient)
|
68
80
|
|
69
|
-
td = Element.new('td')
|
70
|
-
|
71
|
-
valign = opt[:valign]
|
72
|
-
td[:valign] = valign unless valign.blank?
|
73
|
-
|
74
81
|
html << table.to_helper
|
75
|
-
html << td
|
82
|
+
html << '{td}'
|
76
83
|
|
77
84
|
# VML is only added if it is enabled for the project.
|
78
85
|
if ctx.vml_enabled?
|
@@ -117,16 +124,38 @@ module Inkcite
|
|
117
124
|
|
118
125
|
end
|
119
126
|
|
120
|
-
|
127
|
+
html << '<div>'
|
128
|
+
|
129
|
+
html << '{table width=100%}'
|
130
|
+
|
131
|
+
padding = opt[:padding].to_i
|
132
|
+
|
133
|
+
html << hs(padding) if padding > 0
|
134
|
+
|
135
|
+
inner_td = Element.new('td')
|
136
|
+
|
137
|
+
# Height needs to be set on the inner TD to ensure that valign works
|
138
|
+
# in Outlook - which doesn't honor table height but will honor td height.
|
139
|
+
inner_td[:height] = height if height > 0
|
140
|
+
|
141
|
+
valign = opt[:valign]
|
142
|
+
inner_td[:valign] = valign unless valign.blank?
|
143
|
+
|
144
|
+
TD_PASSTHRU_OPS.each do |key|
|
145
|
+
val = opt[key]
|
146
|
+
inner_td[key] = quote(val) unless val.blank?
|
147
|
+
end
|
121
148
|
|
122
149
|
# Font family and other attributes get reset within the v:textbox so allow
|
123
150
|
# the font series of attributes to be applied.
|
124
|
-
mix_font
|
151
|
+
mix_font inner_td, opt, ctx
|
125
152
|
|
126
153
|
# Text alignment within the div.
|
127
|
-
mix_text_align
|
154
|
+
mix_text_align inner_td, opt, ctx
|
128
155
|
|
129
|
-
html <<
|
156
|
+
html << inner_td.to_helper
|
157
|
+
|
158
|
+
html << vs(padding) if padding > 0
|
130
159
|
|
131
160
|
end
|
132
161
|
|
@@ -135,8 +164,13 @@ module Inkcite
|
|
135
164
|
|
136
165
|
private
|
137
166
|
|
138
|
-
|
139
|
-
|
167
|
+
def hs padding
|
168
|
+
Element.new('td', :width => padding, :mobile => 'hide').to_helper + ' {/td}'
|
169
|
+
end
|
170
|
+
|
171
|
+
def vs padding
|
172
|
+
Element.new('div', :height => padding, LINE_HEIGHT => padding, FONT_SIZE => padding, :mobile => 'hide').to_helper + ' {/div}'
|
173
|
+
end
|
140
174
|
|
141
175
|
# These are the parameters that are passed directly from
|
142
176
|
# the provided opt to the {table} rendered within the
|
@@ -148,6 +182,11 @@ module Inkcite
|
|
148
182
|
MOBILE_SRC, MOBILE_BACKGROUND_SIZE, MOBILE_WIDTH
|
149
183
|
]
|
150
184
|
|
185
|
+
TD_PASSTHRU_OPS = [
|
186
|
+
:align, :color, :font, FONT_FAMILY, FONT_SIZE, FONT_WEIGHT, LETTER_SPACING, LINE_HEIGHT,
|
187
|
+
MOBILE_TEXT_ALIGN, :shadow, TEXT_ALIGN, TEXT_SHADOW, TEXT_SHADOW_BLUR, TEXT_SHADOW_OFFSET
|
188
|
+
]
|
189
|
+
|
151
190
|
end
|
152
191
|
end
|
153
192
|
end
|
@@ -11,6 +11,7 @@ module Inkcite
|
|
11
11
|
BACKGROUND_SIZE = :'background-size'
|
12
12
|
BORDER_BOTTOM = :'border-bottom'
|
13
13
|
BORDER_COLLAPSE = :'border-collapse'
|
14
|
+
BORDER_COLOR = :'border-color'
|
14
15
|
BORDER_LEFT = :'border-left'
|
15
16
|
BORDER_RADIUS = :'border-radius'
|
16
17
|
BORDER_RIGHT = :'border-right'
|
@@ -28,6 +29,7 @@ module Inkcite
|
|
28
29
|
MARGIN_LEFT = :'margin-left'
|
29
30
|
MARGIN_RIGHT = :'margin-right'
|
30
31
|
MARGIN_TOP = :'margin-top'
|
32
|
+
MAX_HEIGHT = :'max-height'
|
31
33
|
MAX_WIDTH = :'max-width'
|
32
34
|
PADDING_X = :'padding-x'
|
33
35
|
PADDING_Y = :'padding-y'
|
@@ -40,6 +42,10 @@ module Inkcite
|
|
40
42
|
WEBKIT_ANIMATION = :'-webkit-animation'
|
41
43
|
WHITE_SPACE = :'white-space'
|
42
44
|
|
45
|
+
# Name of the property that allows an outlook-specific src to be specified
|
46
|
+
# for an image.
|
47
|
+
OUTLOOK_SRC = :'outlook-src'
|
48
|
+
|
43
49
|
# CSS direction suffixes including nil/empty for convenience.
|
44
50
|
DIRECTIONS = [ nil, :top, :right, :bottom, :left]
|
45
51
|
|
@@ -122,26 +128,34 @@ module Inkcite
|
|
122
128
|
# Set the background color if the element has one.
|
123
129
|
element.style[BACKGROUND_COLOR] = bgcolor if bgcolor
|
124
130
|
|
131
|
+
# Automatically include background gradient support when
|
132
|
+
# mixing in background color.
|
133
|
+
mix_background_gradient element, opt, ctx
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
def mix_background_gradient element, opt, ctx
|
138
|
+
|
125
139
|
# Background gradient support
|
126
140
|
bggradient = detect_bggradient(opt)
|
127
|
-
|
128
|
-
|
129
|
-
# As a shortcut a gradient can be specified simply by designating
|
130
|
-
# both a bgcolor and the gradient color - this will insert a radial
|
131
|
-
# gradient automatically.
|
132
|
-
if bggradient.start_with?('#')
|
141
|
+
return if none?(bggradient)
|
133
142
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
143
|
+
# As a shortcut a gradient can be specified simply by designating
|
144
|
+
# both a bgcolor and the gradient color - this will insert a radial
|
145
|
+
# gradient automatically.
|
146
|
+
if bggradient.start_with?('#')
|
138
147
|
|
139
|
-
|
140
|
-
|
148
|
+
# If a bgcolor is provided, the gradient goes bgcolor -> bggradient.
|
149
|
+
# Otherwise, it goes bggradient->darker(bggradient)
|
150
|
+
bgcolor = detect_bgcolor(opt)
|
151
|
+
center_color = bgcolor ? bgcolor : bggradient
|
152
|
+
outer_color = bgcolor ? bggradient : Util.darken(bggradient)
|
141
153
|
|
142
|
-
|
154
|
+
bggradient = %Q(radial-gradient(circle at center, #{center_color}, #{outer_color}))
|
143
155
|
end
|
144
156
|
|
157
|
+
element.style[BACKGROUND_IMAGE] = bggradient
|
158
|
+
|
145
159
|
end
|
146
160
|
|
147
161
|
def mix_border element, opt, ctx
|
@@ -213,10 +227,10 @@ module Inkcite
|
|
213
227
|
font
|
214
228
|
end
|
215
229
|
|
216
|
-
def mix_margins element, opt, ctx
|
230
|
+
def mix_margins element, opt, ctx, outlookCompatible=true
|
217
231
|
|
218
232
|
# Outlook supports Margin, not margin.
|
219
|
-
mix_directional element, element.style, opt, ctx, :margin, :Margin, true
|
233
|
+
mix_directional element, element.style, opt, ctx, :margin, outlookCompatible ? :Margin : :margin, true
|
220
234
|
|
221
235
|
end
|
222
236
|
|