dragonfly 0.9.15 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dragonfly might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.gitignore +1 -8
- data/.travis.yml +11 -0
- data/Gemfile +1 -0
- data/History.md +52 -2
- data/LICENSE +1 -1
- data/README.md +38 -95
- data/dev/grid.jpg +0 -0
- data/dev/irbrc.rb +27 -0
- data/dev/rails_template.rb +38 -0
- data/dev/test.ru +56 -0
- data/dev/test_rails +19 -0
- data/dragonfly.gemspec +3 -21
- data/lib/dragonfly.rb +45 -44
- data/lib/dragonfly/app.rb +175 -96
- data/lib/dragonfly/configurable.rb +71 -170
- data/lib/dragonfly/content.rb +211 -0
- data/lib/dragonfly/core_ext/object.rb +1 -6
- data/lib/dragonfly/file_data_store.rb +197 -0
- data/lib/dragonfly/image_magick/analysers/image_properties.rb +23 -0
- data/lib/dragonfly/image_magick/generators/convert.rb +19 -0
- data/lib/dragonfly/image_magick/generators/plain.rb +26 -0
- data/lib/dragonfly/image_magick/generators/plasma.rb +25 -0
- data/lib/dragonfly/image_magick/generators/text.rb +127 -0
- data/lib/dragonfly/image_magick/plugin.rb +83 -0
- data/lib/dragonfly/image_magick/processors/convert.rb +29 -0
- data/lib/dragonfly/image_magick/processors/encode.rb +18 -0
- data/lib/dragonfly/image_magick/processors/thumb.rb +76 -0
- data/lib/dragonfly/job.rb +118 -134
- data/lib/dragonfly/job_endpoint.rb +2 -0
- data/lib/dragonfly/memory_data_store.rb +34 -0
- data/lib/dragonfly/middleware.rb +5 -3
- data/lib/dragonfly/{active_model_extensions.rb → model.rb} +5 -3
- data/lib/dragonfly/{active_model_extensions → model}/attachment.rb +40 -35
- data/lib/dragonfly/{active_model_extensions → model}/attachment_class_methods.rb +36 -40
- data/lib/dragonfly/model/class_methods.rb +109 -0
- data/lib/dragonfly/{active_model_extensions → model}/instance_methods.rb +2 -2
- data/lib/dragonfly/{active_model_extensions → model}/validations.rb +17 -12
- data/lib/dragonfly/railtie.rb +8 -6
- data/lib/dragonfly/register.rb +27 -0
- data/lib/dragonfly/response.rb +47 -52
- data/lib/dragonfly/routed_endpoint.rb +4 -0
- data/lib/dragonfly/serializer.rb +15 -5
- data/lib/dragonfly/server.rb +56 -29
- data/lib/dragonfly/shell.rb +12 -23
- data/lib/dragonfly/spec/data_store_examples.rb +64 -0
- data/lib/dragonfly/temp_object.rb +32 -47
- data/lib/dragonfly/url_attributes.rb +19 -22
- data/lib/dragonfly/url_mapper.rb +3 -0
- data/lib/dragonfly/utils.rb +12 -0
- data/lib/dragonfly/version.rb +1 -1
- data/lib/dragonfly/whitelist.rb +19 -0
- data/lib/rails/generators/dragonfly/USAGE +8 -0
- data/lib/rails/generators/dragonfly/dragonfly_generator.rb +24 -0
- data/lib/rails/generators/dragonfly/templates/initializer.rb.erb +27 -0
- data/samples/gif.gif +0 -0
- data/spec/dragonfly/app_spec.rb +270 -64
- data/spec/dragonfly/configurable_spec.rb +142 -418
- data/spec/dragonfly/content_spec.rb +353 -0
- data/spec/dragonfly/cookie_monster_spec.rb +2 -1
- data/spec/dragonfly/file_data_store_spec.rb +301 -0
- data/spec/dragonfly/image_magick/analysers/image_properties_spec.rb +20 -0
- data/spec/dragonfly/image_magick/generators/convert_spec.rb +19 -0
- data/spec/dragonfly/image_magick/generators/plain_spec.rb +50 -0
- data/spec/dragonfly/image_magick/generators/plasma_spec.rb +32 -0
- data/spec/dragonfly/image_magick/generators/text_spec.rb +77 -0
- data/spec/dragonfly/image_magick/plugin_spec.rb +131 -0
- data/spec/dragonfly/image_magick/processors/convert_spec.rb +56 -0
- data/spec/dragonfly/image_magick/processors/thumb_spec.rb +173 -0
- data/spec/dragonfly/job_endpoint_spec.rb +30 -73
- data/spec/dragonfly/job_spec.rb +280 -608
- data/spec/dragonfly/memory_data_store_spec.rb +20 -0
- data/spec/dragonfly/middleware_spec.rb +47 -27
- data/spec/dragonfly/{active_model_extensions → model}/model_spec.rb +331 -555
- data/spec/dragonfly/model/validations_spec.rb +196 -0
- data/spec/dragonfly/register_spec.rb +35 -0
- data/spec/dragonfly/routed_endpoint_spec.rb +6 -6
- data/spec/dragonfly/serializer_spec.rb +13 -15
- data/spec/dragonfly/server_spec.rb +122 -46
- data/spec/dragonfly/shell_spec.rb +43 -24
- data/spec/dragonfly/temp_object_spec.rb +69 -94
- data/spec/dragonfly/url_attributes_spec.rb +49 -0
- data/spec/dragonfly/utils_spec.rb +32 -0
- data/spec/dragonfly/whitelist_spec.rb +30 -0
- data/spec/dragonfly_spec.rb +43 -0
- data/spec/fixtures/deprecated_stored_content/eggs.bonus +1 -0
- data/spec/fixtures/deprecated_stored_content/eggs.bonus.meta +1 -0
- data/spec/functional/configuration_spec.rb +19 -0
- data/spec/functional/model_urls_spec.rb +43 -41
- data/spec/functional/remote_on_the_fly_spec.rb +14 -16
- data/spec/functional/shell_commands_spec.rb +24 -14
- data/spec/functional/to_response_spec.rb +10 -10
- data/spec/functional/urls_spec.rb +5 -3
- data/spec/spec_helper.rb +23 -28
- data/spec/support/argument_matchers.rb +7 -8
- data/spec/support/image_matchers.rb +14 -36
- data/spec/support/model_helpers.rb +97 -0
- data/spec/support/simple_matchers.rb +12 -0
- metadata +92 -393
- data/.yardopts +0 -29
- data/Rakefile +0 -36
- data/config.ru +0 -14
- data/docs.watchr +0 -1
- data/extra_docs/Analysers.md +0 -68
- data/extra_docs/Caching.md +0 -23
- data/extra_docs/Configuration.md +0 -149
- data/extra_docs/Couch.md +0 -49
- data/extra_docs/DataStorage.md +0 -226
- data/extra_docs/Encoding.md +0 -67
- data/extra_docs/ExampleUseCases.md +0 -116
- data/extra_docs/GeneralUsage.md +0 -105
- data/extra_docs/Generators.md +0 -68
- data/extra_docs/Heroku.md +0 -50
- data/extra_docs/ImageMagick.md +0 -136
- data/extra_docs/Index.md +0 -33
- data/extra_docs/MimeTypes.md +0 -40
- data/extra_docs/Models.md +0 -441
- data/extra_docs/Mongo.md +0 -42
- data/extra_docs/Processing.md +0 -77
- data/extra_docs/Rack.md +0 -52
- data/extra_docs/Rails2.md +0 -57
- data/extra_docs/Rails3.md +0 -56
- data/extra_docs/ServingRemotely.md +0 -104
- data/extra_docs/Sinatra.md +0 -25
- data/extra_docs/URLs.md +0 -203
- data/features/images.feature +0 -47
- data/features/no_processing.feature +0 -14
- data/features/rails.feature +0 -8
- data/features/steps/common_steps.rb +0 -8
- data/features/steps/dragonfly_steps.rb +0 -66
- data/features/steps/rails_steps.rb +0 -40
- data/features/support/env.rb +0 -13
- data/features/support/setup.rb +0 -41
- data/fixtures/rails/files/app/models/album.rb +0 -6
- data/fixtures/rails/files/app/views/albums/new.html.erb +0 -7
- data/fixtures/rails/files/app/views/albums/show.html.erb +0 -6
- data/fixtures/rails/files/config/initializers/dragonfly.rb +0 -4
- data/fixtures/rails/files/features/manage_album_images.feature +0 -38
- data/fixtures/rails/files/features/step_definitions/helper_steps.rb +0 -7
- data/fixtures/rails/files/features/step_definitions/image_steps.rb +0 -25
- data/fixtures/rails/files/features/step_definitions/web_steps.rb +0 -189
- data/fixtures/rails/files/features/support/paths.rb +0 -17
- data/fixtures/rails/files/features/text_images.feature +0 -7
- data/fixtures/rails/template.rb +0 -20
- data/irbrc.rb +0 -19
- data/lib/dragonfly/active_model_extensions/class_methods.rb +0 -98
- data/lib/dragonfly/analyser.rb +0 -58
- data/lib/dragonfly/analysis/file_command_analyser.rb +0 -33
- data/lib/dragonfly/analysis/image_magick_analyser.rb +0 -6
- data/lib/dragonfly/config/heroku.rb +0 -26
- data/lib/dragonfly/config/image_magick.rb +0 -6
- data/lib/dragonfly/config/rails.rb +0 -20
- data/lib/dragonfly/data_storage.rb +0 -11
- data/lib/dragonfly/data_storage/couch_data_store.rb +0 -83
- data/lib/dragonfly/data_storage/file_data_store.rb +0 -144
- data/lib/dragonfly/data_storage/mongo_data_store.rb +0 -96
- data/lib/dragonfly/data_storage/s3data_store.rb +0 -168
- data/lib/dragonfly/encoder.rb +0 -13
- data/lib/dragonfly/encoding/image_magick_encoder.rb +0 -6
- data/lib/dragonfly/function_manager.rb +0 -67
- data/lib/dragonfly/generation/image_magick_generator.rb +0 -6
- data/lib/dragonfly/generator.rb +0 -9
- data/lib/dragonfly/image_magick/analyser.rb +0 -53
- data/lib/dragonfly/image_magick/config.rb +0 -44
- data/lib/dragonfly/image_magick/encoder.rb +0 -57
- data/lib/dragonfly/image_magick/generator.rb +0 -147
- data/lib/dragonfly/image_magick/processor.rb +0 -114
- data/lib/dragonfly/image_magick/utils.rb +0 -46
- data/lib/dragonfly/image_magick_utils.rb +0 -4
- data/lib/dragonfly/job_builder.rb +0 -39
- data/lib/dragonfly/job_definitions.rb +0 -30
- data/lib/dragonfly/loggable.rb +0 -28
- data/lib/dragonfly/processing/image_magick_processor.rb +0 -6
- data/lib/dragonfly/processor.rb +0 -9
- data/lib/dragonfly/rails/images.rb +0 -32
- data/lib/dragonfly/simple_cache.rb +0 -23
- data/spec/dragonfly/active_model_extensions/spec_helper.rb +0 -95
- data/spec/dragonfly/analyser_spec.rb +0 -123
- data/spec/dragonfly/analysis/file_command_analyser_spec.rb +0 -49
- data/spec/dragonfly/data_storage/couch_data_store_spec.rb +0 -84
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +0 -308
- data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +0 -81
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +0 -277
- data/spec/dragonfly/data_storage/shared_data_store_examples.rb +0 -77
- data/spec/dragonfly/function_manager_spec.rb +0 -154
- data/spec/dragonfly/image_magick/analyser_spec.rb +0 -73
- data/spec/dragonfly/image_magick/encoder_spec.rb +0 -46
- data/spec/dragonfly/image_magick/generator_spec.rb +0 -178
- data/spec/dragonfly/image_magick/processor_spec.rb +0 -293
- data/spec/dragonfly/job_builder_spec.rb +0 -37
- data/spec/dragonfly/job_definitions_spec.rb +0 -57
- data/spec/dragonfly/loggable_spec.rb +0 -80
- data/spec/dragonfly/simple_cache_spec.rb +0 -27
- data/spec/dragonfly/url_attributes.rb +0 -47
- data/spec/functional/deprecations_spec.rb +0 -51
- data/spec/functional/image_magick_app_spec.rb +0 -27
- data/spec/test_imagemagick.ru +0 -49
- data/yard/handlers/configurable_attr_handler.rb +0 -38
- data/yard/setup.rb +0 -15
- data/yard/templates/default/fulldoc/html/css/common.css +0 -109
- data/yard/templates/default/layout/html/layout.erb +0 -93
- data/yard/templates/default/module/html/configuration_summary.erb +0 -31
- data/yard/templates/default/module/setup.rb +0 -17
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'dragonfly/utils'
|
5
|
+
|
6
|
+
module Dragonfly
|
7
|
+
class FileDataStore
|
8
|
+
|
9
|
+
# Exceptions
|
10
|
+
class BadUID < RuntimeError; end
|
11
|
+
class UnableToFormUrl < RuntimeError; end
|
12
|
+
|
13
|
+
class MetaStore
|
14
|
+
def write(data_path, meta)
|
15
|
+
File.open(meta_path(data_path), 'wb') do |f|
|
16
|
+
f.write dump(meta)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(data_path)
|
21
|
+
path = meta_path(data_path)
|
22
|
+
File.open(path,'rb'){|f| load(f.read) } if File.exist?(path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy(path)
|
26
|
+
FileUtils.rm_f meta_path(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def meta_path(data_path)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def dump(meta)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
def load(string)
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class YAMLMetaStore < MetaStore
|
45
|
+
def meta_path(data_path)
|
46
|
+
"#{data_path}.meta.yml"
|
47
|
+
end
|
48
|
+
|
49
|
+
def dump(meta)
|
50
|
+
YAML.dump(meta)
|
51
|
+
end
|
52
|
+
|
53
|
+
def load(string)
|
54
|
+
YAML.load(string)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class MarshalMetaStore < MetaStore
|
59
|
+
def meta_path(data_path)
|
60
|
+
"#{data_path}.meta"
|
61
|
+
end
|
62
|
+
|
63
|
+
def dump(meta)
|
64
|
+
Marshal.dump(meta)
|
65
|
+
end
|
66
|
+
|
67
|
+
def load(string)
|
68
|
+
Utils.stringify_keys Marshal.load(string)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(opts={})
|
73
|
+
self.root_path = opts[:root_path] || 'dragonfly'
|
74
|
+
self.server_root = opts[:server_root]
|
75
|
+
self.store_meta = opts[:store_meta]
|
76
|
+
@meta_store = YAMLMetaStore.new
|
77
|
+
@deprecated_meta_store = MarshalMetaStore.new
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_writer :store_meta
|
81
|
+
attr_reader :root_path, :server_root
|
82
|
+
|
83
|
+
def root_path=(path)
|
84
|
+
@root_path = path ? path.to_s : nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def server_root=(path)
|
88
|
+
@server_root = path ? path.to_s : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def store_meta?
|
92
|
+
@store_meta != false # Default to true if not set
|
93
|
+
end
|
94
|
+
|
95
|
+
def write(content, opts={})
|
96
|
+
relative_path = if opts[:path]
|
97
|
+
opts[:path]
|
98
|
+
else
|
99
|
+
filename = content.name || 'file'
|
100
|
+
relative_path = relative_path_for(filename)
|
101
|
+
end
|
102
|
+
|
103
|
+
path = absolute(relative_path)
|
104
|
+
until !File.exist?(path)
|
105
|
+
path = disambiguate(path)
|
106
|
+
end
|
107
|
+
content.to_file(path).close
|
108
|
+
meta_store.write(path, content.meta) if store_meta?
|
109
|
+
|
110
|
+
relative(path)
|
111
|
+
end
|
112
|
+
|
113
|
+
def read(relative_path)
|
114
|
+
validate_path!(relative_path)
|
115
|
+
path = absolute(relative_path)
|
116
|
+
pathname = Pathname.new(path)
|
117
|
+
return nil unless pathname.exist?
|
118
|
+
[
|
119
|
+
pathname,
|
120
|
+
(
|
121
|
+
if store_meta?
|
122
|
+
meta_store.read(path) || deprecated_meta_store.read(path) || {}
|
123
|
+
end
|
124
|
+
)
|
125
|
+
]
|
126
|
+
end
|
127
|
+
|
128
|
+
def destroy(relative_path)
|
129
|
+
validate_path!(relative_path)
|
130
|
+
path = absolute(relative_path)
|
131
|
+
FileUtils.rm path
|
132
|
+
meta_store.destroy(path)
|
133
|
+
purge_empty_directories(relative_path)
|
134
|
+
rescue Errno::ENOENT => e
|
135
|
+
Dragonfly.warn("#{self.class.name} destroy error: #{e}")
|
136
|
+
end
|
137
|
+
|
138
|
+
def url_for(relative_path, opts={})
|
139
|
+
if server_root.nil?
|
140
|
+
raise UnableToFormUrl, "you need to configure server_root for #{self.class.name} in order to form urls"
|
141
|
+
else
|
142
|
+
_, __, path = absolute(relative_path).partition(server_root)
|
143
|
+
if path.empty?
|
144
|
+
raise UnableToFormUrl, "couldn't form url for uid #{relative_path.inspect} with root_path #{root_path.inspect} and server_root #{server_root.inspect}"
|
145
|
+
else
|
146
|
+
path
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def disambiguate(path)
|
152
|
+
dirname = File.dirname(path)
|
153
|
+
basename = File.basename(path, '.*')
|
154
|
+
extname = File.extname(path)
|
155
|
+
"#{dirname}/#{basename}_#{(Time.now.usec*10 + rand(100)).to_s(32)}#{extname}"
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
attr_reader :meta_store, :deprecated_meta_store
|
161
|
+
|
162
|
+
def absolute(relative_path)
|
163
|
+
relative_path.to_s == '.' ? root_path : File.join(root_path, relative_path)
|
164
|
+
end
|
165
|
+
|
166
|
+
def relative(absolute_path)
|
167
|
+
absolute_path[/^#{Regexp.escape root_path}\/?(.*)$/, 1]
|
168
|
+
end
|
169
|
+
|
170
|
+
def directory_empty?(path)
|
171
|
+
Dir.entries(path) == ['.','..']
|
172
|
+
end
|
173
|
+
|
174
|
+
def root_path?(dir)
|
175
|
+
root_path == dir
|
176
|
+
end
|
177
|
+
|
178
|
+
def relative_path_for(filename)
|
179
|
+
time = Time.now
|
180
|
+
"#{time.strftime '%Y/%m/%d/%H_%M_%S'}_#{time.usec.to_s(36)}_#{filename.gsub(/[^\w.]+/,'_')}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def purge_empty_directories(path)
|
184
|
+
containing_directory = Pathname.new(path).dirname
|
185
|
+
containing_directory.ascend do |relative_dir|
|
186
|
+
dir = absolute(relative_dir)
|
187
|
+
FileUtils.rmdir dir if directory_empty?(dir) && !root_path?(dir)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def validate_path!(uid)
|
192
|
+
raise BadUID, uid if Utils.blank?(uid) || uid['../']
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ImageMagick
|
3
|
+
module Analysers
|
4
|
+
class ImageProperties
|
5
|
+
|
6
|
+
def call(content)
|
7
|
+
identify_command = content.env[:identify_command] || 'identify'
|
8
|
+
details = content.shell_eval do |path|
|
9
|
+
"#{identify_command} -ping -format '%m %w %h' #{path}"
|
10
|
+
end
|
11
|
+
format, width, height = details.split
|
12
|
+
{
|
13
|
+
'format' => format.downcase,
|
14
|
+
'width' => width.to_i,
|
15
|
+
'height' => height.to_i
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ImageMagick
|
3
|
+
module Generators
|
4
|
+
class Convert
|
5
|
+
|
6
|
+
def call(content, args, format)
|
7
|
+
format = format.to_s
|
8
|
+
convert_command = content.env[:convert_command] || 'convert'
|
9
|
+
content.shell_generate :ext => format do |path|
|
10
|
+
"#{convert_command} #{args} #{path}"
|
11
|
+
end
|
12
|
+
content.add_meta('format' => format)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ImageMagick
|
3
|
+
module Generators
|
4
|
+
class Plain
|
5
|
+
|
6
|
+
def call(content, width, height, opts={})
|
7
|
+
format = extract_format(opts)
|
8
|
+
colour = opts['colour'] || opts['color'] || 'white'
|
9
|
+
content.generate!(:convert, "-size #{width}x#{height} xc:#{colour}", format)
|
10
|
+
content.add_meta('format' => format, 'name' => "plain.#{format}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def update_url(url_attributes, width, height, opts={})
|
14
|
+
url_attributes.name = "plain.#{extract_format(opts)}"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def extract_format(opts)
|
20
|
+
opts['format'] || 'png'
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ImageMagick
|
3
|
+
module Generators
|
4
|
+
class Plasma
|
5
|
+
|
6
|
+
def call(content, width, height, opts={})
|
7
|
+
format = extract_format(opts)
|
8
|
+
content.generate!(:convert, "-size #{width}x#{height} plasma:fractal", format)
|
9
|
+
content.add_meta('format' => format, 'name' => "plasma.#{format}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_url(url_attributes, width, height, opts={})
|
13
|
+
url_attributes.name = "plasma.#{extract_format(opts)}"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def extract_format(opts)
|
19
|
+
opts['format'] || 'png'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'dragonfly/hash_with_css_style_keys'
|
2
|
+
|
3
|
+
module Dragonfly
|
4
|
+
module ImageMagick
|
5
|
+
module Generators
|
6
|
+
class Text
|
7
|
+
|
8
|
+
FONT_STYLES = {
|
9
|
+
'normal' => 'normal',
|
10
|
+
'italic' => 'italic',
|
11
|
+
'oblique' => 'oblique'
|
12
|
+
}
|
13
|
+
|
14
|
+
FONT_STRETCHES = {
|
15
|
+
'normal' => 'normal',
|
16
|
+
'semi-condensed' => 'semi-condensed',
|
17
|
+
'condensed' => 'condensed',
|
18
|
+
'extra-condensed' => 'extra-condensed',
|
19
|
+
'ultra-condensed' => 'ultra-condensed',
|
20
|
+
'semi-expanded' => 'semi-expanded',
|
21
|
+
'expanded' => 'expanded',
|
22
|
+
'extra-expanded' => 'extra-expanded',
|
23
|
+
'ultra-expanded' => 'ultra-expanded'
|
24
|
+
}
|
25
|
+
|
26
|
+
FONT_WEIGHTS = {
|
27
|
+
'normal' => 'normal',
|
28
|
+
'bold' => 'bold',
|
29
|
+
'bolder' => 'bolder',
|
30
|
+
'lighter' => 'lighter',
|
31
|
+
'100' => 100,
|
32
|
+
'200' => 200,
|
33
|
+
'300' => 300,
|
34
|
+
'400' => 400,
|
35
|
+
'500' => 500,
|
36
|
+
'600' => 600,
|
37
|
+
'700' => 700,
|
38
|
+
'800' => 800,
|
39
|
+
'900' => 900
|
40
|
+
}
|
41
|
+
|
42
|
+
def update_url(url_attributes, string, opts={})
|
43
|
+
url_attributes.name = "text.#{extract_format(opts)}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(content, string, opts={})
|
47
|
+
opts = HashWithCssStyleKeys[opts]
|
48
|
+
args = []
|
49
|
+
format = extract_format(opts)
|
50
|
+
background = opts['background_color'] || 'none'
|
51
|
+
font_size = (opts['font_size'] || 12).to_i
|
52
|
+
escaped_string = "\"#{string.gsub(/"/, '\"')}\""
|
53
|
+
|
54
|
+
# Settings
|
55
|
+
args.push("-gravity NorthWest")
|
56
|
+
args.push("-antialias")
|
57
|
+
args.push("-pointsize #{font_size}")
|
58
|
+
args.push("-font \"#{opts['font']}\"") if opts['font']
|
59
|
+
args.push("-family '#{opts['font_family']}'") if opts['font_family']
|
60
|
+
args.push("-fill #{opts['color']}") if opts['color']
|
61
|
+
args.push("-stroke #{opts['stroke_color']}") if opts['stroke_color']
|
62
|
+
args.push("-style #{FONT_STYLES[opts['font_style']]}") if opts['font_style']
|
63
|
+
args.push("-stretch #{FONT_STRETCHES[opts['font_stretch']]}") if opts['font_stretch']
|
64
|
+
args.push("-weight #{FONT_WEIGHTS[opts['font_weight']]}") if opts['font_weight']
|
65
|
+
args.push("-background #{background}")
|
66
|
+
args.push("label:#{escaped_string}")
|
67
|
+
|
68
|
+
# Padding
|
69
|
+
pt, pr, pb, pl = parse_padding_string(opts['padding']) if opts['padding']
|
70
|
+
padding_top = (opts['padding_top'] || pt || 0)
|
71
|
+
padding_right = (opts['padding_right'] || pr || 0)
|
72
|
+
padding_bottom = (opts['padding_bottom'] || pb || 0)
|
73
|
+
padding_left = (opts['padding_left'] || pl || 0)
|
74
|
+
|
75
|
+
content.generate!(:convert, args.join(' '), format)
|
76
|
+
|
77
|
+
if (padding_top || padding_right || padding_bottom || padding_left)
|
78
|
+
dimensions = content.analyse(:image_properties)
|
79
|
+
text_width = dimensions['width']
|
80
|
+
text_height = dimensions['height']
|
81
|
+
width = padding_left + text_width + padding_right
|
82
|
+
height = padding_top + text_height + padding_bottom
|
83
|
+
|
84
|
+
args = args.slice(0, args.length - 2)
|
85
|
+
args.push("-size #{width}x#{height}")
|
86
|
+
args.push("xc:#{background}")
|
87
|
+
args.push("-annotate 0x0+#{padding_left}+#{padding_top} #{escaped_string}")
|
88
|
+
content.generate!(:convert, args.join(' '), format)
|
89
|
+
end
|
90
|
+
|
91
|
+
content.add_meta('format' => format, 'name' => "text.#{format}")
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def extract_format(opts)
|
97
|
+
opts['format'] || 'png'
|
98
|
+
end
|
99
|
+
|
100
|
+
# Use css-style padding declaration, i.e.
|
101
|
+
# 10 (all sides)
|
102
|
+
# 10 5 (top/bottom, left/right)
|
103
|
+
# 10 5 10 (top, left/right, bottom)
|
104
|
+
# 10 5 10 5 (top, right, bottom, left)
|
105
|
+
def parse_padding_string(str)
|
106
|
+
padding_parts = str.gsub('px','').split(/\s+/).map{|px| px.to_i}
|
107
|
+
case padding_parts.size
|
108
|
+
when 1
|
109
|
+
p = padding_parts.first
|
110
|
+
[p,p,p,p]
|
111
|
+
when 2
|
112
|
+
p,q = padding_parts
|
113
|
+
[p,q,p,q]
|
114
|
+
when 3
|
115
|
+
p,q,r = padding_parts
|
116
|
+
[p,q,r,q]
|
117
|
+
when 4
|
118
|
+
padding_parts
|
119
|
+
else raise ArgumentError, "Couldn't parse padding string '#{str}' - should be a css-style string"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'dragonfly/image_magick/analysers/image_properties'
|
2
|
+
require 'dragonfly/image_magick/generators/convert'
|
3
|
+
require 'dragonfly/image_magick/generators/plain'
|
4
|
+
require 'dragonfly/image_magick/generators/plasma'
|
5
|
+
require 'dragonfly/image_magick/generators/text'
|
6
|
+
require 'dragonfly/image_magick/processors/convert'
|
7
|
+
require 'dragonfly/image_magick/processors/encode'
|
8
|
+
require 'dragonfly/image_magick/processors/thumb'
|
9
|
+
|
10
|
+
module Dragonfly
|
11
|
+
module ImageMagick
|
12
|
+
|
13
|
+
# The ImageMagick Plugin registers an app with generators, analysers and processors.
|
14
|
+
# Look at the source code for #call to see exactly how it configures the app.
|
15
|
+
class Plugin
|
16
|
+
|
17
|
+
def call(app, opts={})
|
18
|
+
# ENV
|
19
|
+
app.env[:convert_command] = opts[:convert_command] || 'convert'
|
20
|
+
app.env[:identify_command] = opts[:identify_command] || 'identify'
|
21
|
+
|
22
|
+
# Analysers
|
23
|
+
app.add_analyser :image_properties, ImageMagick::Analysers::ImageProperties.new
|
24
|
+
app.add_analyser :width do |content|
|
25
|
+
content.analyse(:image_properties)['width']
|
26
|
+
end
|
27
|
+
app.add_analyser :height do |content|
|
28
|
+
content.analyse(:image_properties)['height']
|
29
|
+
end
|
30
|
+
app.add_analyser :format do |content|
|
31
|
+
content.analyse(:image_properties)['format']
|
32
|
+
end
|
33
|
+
app.add_analyser :aspect_ratio do |content|
|
34
|
+
attrs = content.analyse(:image_properties)
|
35
|
+
attrs['width'].to_f / attrs['height']
|
36
|
+
end
|
37
|
+
app.add_analyser :portrait do |content|
|
38
|
+
attrs = content.analyse(:image_properties)
|
39
|
+
attrs['width'] <= attrs['height']
|
40
|
+
end
|
41
|
+
app.add_analyser :landscape do |content|
|
42
|
+
!content.analyse(:portrait)
|
43
|
+
end
|
44
|
+
app.add_analyser :image do |content|
|
45
|
+
begin
|
46
|
+
content.analyse(:image_properties)['format'] != 'pdf'
|
47
|
+
rescue Shell::CommandFailed
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Aliases
|
53
|
+
app.define(:portrait?) { portrait }
|
54
|
+
app.define(:landscape?) { landscape }
|
55
|
+
app.define(:image?) { image }
|
56
|
+
|
57
|
+
# Generators
|
58
|
+
app.add_generator :convert, ImageMagick::Generators::Convert.new
|
59
|
+
app.add_generator :plain, ImageMagick::Generators::Plain.new
|
60
|
+
app.add_generator :plasma, ImageMagick::Generators::Plasma.new
|
61
|
+
app.add_generator :text, ImageMagick::Generators::Text.new
|
62
|
+
|
63
|
+
# Processors
|
64
|
+
app.add_processor :convert, Processors::Convert.new
|
65
|
+
app.add_processor :encode, Processors::Encode.new
|
66
|
+
app.add_processor :thumb, Processors::Thumb.new
|
67
|
+
app.add_processor :rotate do |content, amount|
|
68
|
+
content.process!(:convert, "-rotate #{amount}")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Extra methods
|
72
|
+
app.define :identify do |args=""|
|
73
|
+
shell_eval do |path|
|
74
|
+
"#{app.env[:identify_command]} #{args} #{path}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|