mojo_magick 0.5.5 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.circleci/config.yml +25 -0
- data/.github/dependabot.yml +8 -0
- data/.rubocop-common.yml +99 -0
- data/.rubocop-performance.yml +2 -0
- data/.rubocop-rspec.yml +33 -0
- data/.rubocop.yml +11 -0
- data/.ruby-version +1 -1
- data/Gemfile +6 -1
- data/Gemfile.lock +58 -10
- data/README.md +35 -4
- data/Rakefile +14 -12
- data/examples/animated_gif.rb +11 -14
- data/examples/composite.rb +11 -14
- data/init.rb +1 -3
- data/lib/image_magick/fonts.rb +4 -11
- data/lib/mojo_magick.rb +116 -103
- data/lib/mojo_magick/command_status.rb +3 -2
- data/lib/mojo_magick/commands.rb +32 -0
- data/lib/mojo_magick/errors.rb +1 -1
- data/lib/mojo_magick/font.rb +4 -7
- data/lib/mojo_magick/opt_builder.rb +24 -35
- data/lib/mojo_magick/util/font_parser.rb +43 -0
- data/lib/mojo_magick/util/parser.rb +11 -60
- data/lib/mojo_magick/version.rb +1 -1
- data/mojo_magick.gemspec +20 -15
- data/test/fixtures/roll with it.jpg +0 -0
- data/test/font_parser_test.rb +29 -0
- data/test/font_test.rb +22 -28
- data/test/mojo_magick_test.rb +288 -283
- data/test/opt_builder_test.rb +105 -89
- data/test/test_helper.rb +7 -5
- metadata +78 -18
- data/lib/image_magick/resource_limits.rb +0 -96
- data/lib/initializers/hash.rb +0 -8
- data/test/fonts_test.rb +0 -12
- data/test/parser_test.rb +0 -30
- data/test/resource_limits_test.rb +0 -51
data/init.rb
CHANGED
data/lib/image_magick/fonts.rb
CHANGED
@@ -1,15 +1,8 @@
|
|
1
1
|
module ImageMagick
|
2
|
-
|
3
|
-
def
|
4
|
-
|
5
|
-
raw_fonts
|
6
|
-
self.raw_command('identify', '-list font')
|
7
|
-
rescue Exception => ex
|
8
|
-
puts ex
|
9
|
-
puts "Failed to execute font list with raw_command - trying straight up execute"
|
10
|
-
`convert -list font`
|
11
|
-
end
|
12
|
-
@parser.parse_fonts(raw_fonts)
|
2
|
+
class Fonts
|
3
|
+
def self.all
|
4
|
+
raw_fonts = MojoMagick::Commands.raw_command("identify", "-list", "font")
|
5
|
+
MojoMagick::Util::FontParser.new(raw_fonts).parse
|
13
6
|
end
|
14
7
|
end
|
15
8
|
end
|
data/lib/mojo_magick.rb
CHANGED
@@ -1,16 +1,12 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require File::join(cwd, 'mojo_magick/opt_builder')
|
11
|
-
require File::join(cwd, 'mojo_magick/font')
|
12
|
-
require 'tempfile'
|
13
|
-
|
1
|
+
require "tempfile"
|
2
|
+
require "open3"
|
3
|
+
require_relative "./mojo_magick/util/font_parser"
|
4
|
+
require_relative "./mojo_magick/errors"
|
5
|
+
require_relative "./mojo_magick/command_status"
|
6
|
+
require_relative "./mojo_magick/commands"
|
7
|
+
require_relative "./image_magick/fonts"
|
8
|
+
require_relative "./mojo_magick/opt_builder"
|
9
|
+
require_relative "./mojo_magick/font"
|
14
10
|
|
15
11
|
# MojoMagick is a stateless set of module methods which present a convient interface
|
16
12
|
# for accessing common tasks for ImageMagick command line library.
|
@@ -36,7 +32,8 @@ require 'tempfile'
|
|
36
32
|
#
|
37
33
|
# Equivalent to:
|
38
34
|
#
|
39
|
-
# MojoMagick::raw_command('convert', 'source.jpg -crop 250x250+0+0
|
35
|
+
# MojoMagick::Commands.raw_command('convert', 'source.jpg -crop 250x250+0+0\
|
36
|
+
# +repage -strip -set comment "my favorite file" dest.jpg')
|
40
37
|
#
|
41
38
|
# Example #mogrify usage:
|
42
39
|
#
|
@@ -44,7 +41,7 @@ require 'tempfile'
|
|
44
41
|
#
|
45
42
|
# Equivalent to:
|
46
43
|
#
|
47
|
-
# MojoMagick::raw_command('mogrify', '-shave 10x10 image.jpg')
|
44
|
+
# MojoMagick::Commands.raw_command('mogrify', '-shave 10x10 image.jpg')
|
48
45
|
#
|
49
46
|
# Example showing some additional options:
|
50
47
|
#
|
@@ -61,134 +58,150 @@ require 'tempfile'
|
|
61
58
|
# bang (!) can be appended to command names to use the '+' versions
|
62
59
|
# instead of '-' versions.
|
63
60
|
#
|
64
|
-
module MojoMagick
|
65
61
|
|
66
|
-
|
67
|
-
|
68
|
-
|
62
|
+
module MojoMagickDeprecations
|
63
|
+
# rubocop:disable Naming/AccessorMethodName
|
64
|
+
def get_fonts
|
65
|
+
warn "DEPRECATION WARNING: #{__method__} is deprecated and will be removed with the next minor version release."\
|
66
|
+
" Please use `available_fonts` instead"
|
67
|
+
MojoMagick.available_fonts
|
68
|
+
end
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
# rubocop:enable Naming/AccessorMethodName
|
71
|
+
### Moved to `Commands`
|
72
|
+
def execute!(*args)
|
73
|
+
warn "DEPRECATION WARNING: #{__method__} is deprecated and will be removed with the next minor version release."\
|
74
|
+
" Please use `MojoMagick::Commands.execute!` instead"
|
75
|
+
MojoMagick::Commands.send(:execute!, *args)
|
73
76
|
end
|
74
77
|
|
75
|
-
def
|
76
|
-
#
|
77
|
-
|
78
|
-
|
79
|
-
execute = "#{command} #{get_limits_as_params} #{args}"
|
80
|
-
out, outerr, status = Open3.capture3(execute)
|
81
|
-
CommandStatus.new execute, out, outerr, status
|
82
|
-
rescue Exception => e
|
83
|
-
raise MojoError, "#{e.class}: #{e.message}"
|
84
|
-
end
|
78
|
+
def execute(*args)
|
79
|
+
warn "DEPRECATION WARNING: #{__method__} is deprecated and will be removed with the next minor version release."\
|
80
|
+
" Please use `MojoMagick::Commands.execute!` instead"
|
81
|
+
MojoMagick::Commands.send(:execute, *args)
|
85
82
|
end
|
86
|
-
|
87
|
-
def
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
if !status.success?
|
92
|
-
err_msg = options[:err_msg] || "MojoMagick command failed: #{command}."
|
93
|
-
raise(MojoFailed, "#{err_msg} (Exit status: #{status.exit_code})\n Command: #{status.command}\n Error: #{status.error}")
|
94
|
-
end
|
95
|
-
status.return_value
|
83
|
+
|
84
|
+
def raw_command(*args)
|
85
|
+
warn "DEPRECATION WARNING: #{__method__} is deprecated and will be removed with the next minor version release."\
|
86
|
+
" Please use `MojoMagick::Commands.execute!` instead"
|
87
|
+
MojoMagick::Commands.raw_command(*args)
|
96
88
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
89
|
+
end
|
90
|
+
|
91
|
+
module MojoMagick
|
92
|
+
extend MojoMagickDeprecations
|
93
|
+
def self.windows?
|
94
|
+
!RUBY_PLATFORM.include(win32)
|
100
95
|
end
|
101
96
|
|
102
|
-
def
|
97
|
+
def self.shrink(source_file, dest_file, options)
|
103
98
|
opts = options.dup
|
104
99
|
opts.delete(:expand_only)
|
105
|
-
MojoMagick
|
100
|
+
MojoMagick.resize(source_file, dest_file, opts.merge(shrink_only: true))
|
106
101
|
end
|
107
102
|
|
108
|
-
def
|
103
|
+
def self.expand(source_file, dest_file, options)
|
109
104
|
opts = options.dup
|
110
105
|
opts.delete(:shrink_only)
|
111
|
-
MojoMagick
|
106
|
+
MojoMagick.resize(source_file, dest_file, opts.merge(expand_only: true))
|
112
107
|
end
|
113
108
|
|
114
109
|
# resizes an image and returns the filename written to
|
115
110
|
# options:
|
116
111
|
# :width / :height => scale to these dimensions
|
117
|
-
# :scale => pass scale options such as ">" to force shrink scaling only or
|
112
|
+
# :scale => pass scale options such as ">" to force shrink scaling only or
|
113
|
+
# "!" to force absolute width/height scaling (do not preserve aspect ratio)
|
118
114
|
# :percent => scale image to this percentage (do not specify :width/:height in this case)
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
scale_options << ">" unless options[:shrink_only].nil?
|
123
|
-
scale_options << "<" unless options[:expand_only].nil?
|
124
|
-
scale_options << "!" unless options[:absolute_aspect].nil?
|
125
|
-
scale_options << "^" unless options[:fill].nil?
|
126
|
-
scale_options = scale_options.join
|
127
|
-
|
115
|
+
def self.resize(source_file, dest_file, options)
|
116
|
+
scale_options = extract_scale_options(options)
|
117
|
+
geometry = extract_geometry_options(options)
|
128
118
|
extras = []
|
129
|
-
if !options[:width].nil? && !options[:height].nil?
|
130
|
-
geometry = "#{options[:width]}X#{options[:height]}"
|
131
|
-
elsif !options[:percent].nil?
|
132
|
-
geometry = "#{options[:percent]}%"
|
133
|
-
else
|
134
|
-
raise MojoMagickError, "Unknown options for method resize: #{options.inspect}"
|
135
|
-
end
|
136
119
|
if !options[:fill].nil? && !options[:crop].nil?
|
137
|
-
extras << "-gravity
|
138
|
-
extras << "
|
120
|
+
extras << "-gravity"
|
121
|
+
extras << "Center"
|
122
|
+
extras << "-extent"
|
123
|
+
extras << geometry.to_s
|
139
124
|
end
|
140
|
-
|
125
|
+
Commands.raw_command("convert",
|
126
|
+
source_file,
|
127
|
+
"-resize", "#{geometry}#{scale_options}",
|
128
|
+
*extras, dest_file)
|
141
129
|
dest_file
|
142
130
|
end
|
143
131
|
|
144
|
-
def
|
132
|
+
def self.convert(source = nil, dest = nil)
|
133
|
+
opts = OptBuilder.new
|
134
|
+
opts.file source if source
|
135
|
+
yield opts
|
136
|
+
opts.file dest if dest
|
137
|
+
|
138
|
+
Commands.raw_command("convert", *opts.to_a)
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.mogrify(dest = nil)
|
142
|
+
opts = OptBuilder.new
|
143
|
+
yield opts
|
144
|
+
opts.file dest if dest
|
145
|
+
Commands.raw_command("mogrify", *opts.to_a)
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.available_fonts
|
145
149
|
# returns width, height of image if available, nil if not
|
146
150
|
Font.all
|
147
151
|
end
|
148
152
|
|
153
|
+
def self.get_format(source_file, format_string)
|
154
|
+
Commands.raw_command("identify", "-format", format_string, source_file)
|
155
|
+
end
|
156
|
+
|
149
157
|
# returns an empty hash or a hash with :width and :height set (e.g. {:width => INT, :height => INT})
|
150
158
|
# raises MojoFailed when results are indeterminate (width and height could not be determined)
|
151
|
-
def
|
159
|
+
def self.get_image_size(source_file)
|
152
160
|
# returns width, height of image if available, nil if not
|
153
|
-
retval =
|
154
|
-
return {}
|
155
|
-
|
161
|
+
retval = get_format(source_file, "w:%w h:%h")
|
162
|
+
return {} unless retval
|
163
|
+
|
164
|
+
width = retval.match(/w:([0-9]+) /)
|
156
165
|
width = width ? width[1].to_i : nil
|
157
|
-
height = retval.match(
|
166
|
+
height = retval.match(/h:([0-9]+)/)
|
158
167
|
height = height ? height[1].to_i : nil
|
159
168
|
raise(MojoFailed, "Indeterminate results in get_image_size: #{source_file}") if !height || !width
|
160
|
-
{:width=>width, :height=>height}
|
161
|
-
end
|
162
169
|
|
163
|
-
|
164
|
-
opts = OptBuilder.new
|
165
|
-
opts.file source if source
|
166
|
-
yield opts
|
167
|
-
opts.file dest if dest
|
168
|
-
raw_command('convert', opts.to_s)
|
170
|
+
{ width: width, height: height }
|
169
171
|
end
|
170
172
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
173
|
+
def self.tempfile(*opts)
|
174
|
+
data = opts[0]
|
175
|
+
rest = opts[1]
|
176
|
+
ext = rest && rest[:format]
|
177
|
+
file = Tempfile.new(["mojo", ext ? ".#{ext}" : ""])
|
178
|
+
file.binmode
|
179
|
+
file.write(data)
|
180
|
+
file.path
|
181
|
+
ensure
|
182
|
+
file.close
|
176
183
|
end
|
177
184
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
ensure
|
190
|
-
file.close
|
185
|
+
class << self
|
186
|
+
private
|
187
|
+
|
188
|
+
def extract_geometry_options(options)
|
189
|
+
if !options[:width].nil? && !options[:height].nil?
|
190
|
+
"#{options[:width]}X#{options[:height]}"
|
191
|
+
elsif !options[:percent].nil?
|
192
|
+
"#{options[:percent]}%"
|
193
|
+
else
|
194
|
+
raise MojoMagickError, "Resize requires width and height or percentage: #{options.inspect}"
|
195
|
+
end
|
191
196
|
end
|
192
|
-
end
|
193
197
|
|
194
|
-
|
198
|
+
def extract_scale_options(options)
|
199
|
+
[].tap { |scale_options|
|
200
|
+
scale_options << ">" unless options[:shrink_only].nil?
|
201
|
+
scale_options << "<" unless options[:expand_only].nil?
|
202
|
+
scale_options << "!" unless options[:absolute_aspect].nil?
|
203
|
+
scale_options << "^" unless options[:fill].nil?
|
204
|
+
}.join
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module MojoMagick
|
2
|
-
|
2
|
+
CommandStatus = Struct.new(:command, :return_value, :error, :system_status) do
|
3
3
|
def success?
|
4
4
|
system_status.success?
|
5
5
|
end
|
6
|
+
|
6
7
|
def exit_code
|
7
|
-
system_status.exitstatus ||
|
8
|
+
system_status.exitstatus || "unknown"
|
8
9
|
end
|
9
10
|
end
|
10
11
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative "opt_builder"
|
2
|
+
|
3
|
+
module MojoMagick
|
4
|
+
class Commands
|
5
|
+
def self.raw_command(*args)
|
6
|
+
execute!(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
private
|
11
|
+
|
12
|
+
def execute(command, *args)
|
13
|
+
execute = "#{command} #{args}"
|
14
|
+
out, outerr, status = Open3.capture3(command, *args.map(&:to_s))
|
15
|
+
CommandStatus.new execute, out, outerr, status
|
16
|
+
rescue StandardError => e
|
17
|
+
raise MojoError, "#{e.class}: #{e.message}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute!(command, *args)
|
21
|
+
status = execute(command, *args)
|
22
|
+
unless status.success?
|
23
|
+
err_msg = "MojoMagick command failed: #{command}."
|
24
|
+
raise(MojoFailed, "#{err_msg} (Exit status: #{status.exit_code})\n" \
|
25
|
+
" Command: #{status.command}\n" \
|
26
|
+
" Error: #{status.error}")
|
27
|
+
end
|
28
|
+
status.return_value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/mojo_magick/errors.rb
CHANGED
data/lib/mojo_magick/font.rb
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
module MojoMagick
|
2
|
-
|
3
2
|
class Font
|
4
|
-
|
5
3
|
attr_accessor :name, :family, :style, :stretch, :weight, :glyphs
|
6
4
|
|
7
5
|
def valid?
|
8
|
-
!
|
6
|
+
!name.nil?
|
9
7
|
end
|
10
8
|
|
11
9
|
def initialize(property_hash = {})
|
12
|
-
|
13
|
-
[:name, :family, :style, :stretch, :weight, :glyphs].each do |f|
|
10
|
+
%i[name family style stretch weight glyphs].each do |f|
|
14
11
|
setter = "#{f}="
|
15
|
-
|
12
|
+
send(setter, property_hash[f])
|
16
13
|
end
|
17
14
|
end
|
18
15
|
|
19
16
|
def self.all
|
20
|
-
ImageMagick::
|
17
|
+
ImageMagick::Fonts.all
|
21
18
|
end
|
22
19
|
end
|
23
20
|
end
|
@@ -17,9 +17,7 @@ module MojoMagick
|
|
17
17
|
|
18
18
|
# Add files to command line, formatted if necessary
|
19
19
|
def file(*args)
|
20
|
-
|
21
|
-
add_formatted arg
|
22
|
-
end
|
20
|
+
@opts << args
|
23
21
|
self
|
24
22
|
end
|
25
23
|
alias files file
|
@@ -29,32 +27,26 @@ module MojoMagick
|
|
29
27
|
end
|
30
28
|
|
31
29
|
# annotate takes non-standard args
|
32
|
-
def annotate(*args)
|
33
|
-
@opts <<
|
34
|
-
arguments = args.join
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
arguments.each do |arg|
|
39
|
-
add_formatted arg
|
40
|
-
end
|
30
|
+
def annotate(*args, geometry: 0)
|
31
|
+
@opts << "-annotate"
|
32
|
+
arguments = [args.join]
|
33
|
+
arguments.unshift geometry.to_s
|
34
|
+
@opts << arguments
|
41
35
|
end
|
42
36
|
|
43
37
|
# Create a temporary file for the given image and add to command line
|
44
38
|
def format(*args)
|
45
|
-
@opts <<
|
46
|
-
|
47
|
-
add_formatted arg
|
48
|
-
end
|
39
|
+
@opts << "-format"
|
40
|
+
@opts << args
|
49
41
|
end
|
50
42
|
|
51
43
|
def blob(*args)
|
52
44
|
data = args[0]
|
53
45
|
opts = args[1] || {}
|
54
|
-
opts.each do |k,v|
|
55
|
-
send(k.to_s,v.to_s)
|
46
|
+
opts.each do |k, v|
|
47
|
+
send(k.to_s, v.to_s)
|
56
48
|
end
|
57
|
-
tmpfile = MojoMagick
|
49
|
+
tmpfile = MojoMagick.tempfile(data, opts)
|
58
50
|
file tmpfile
|
59
51
|
end
|
60
52
|
|
@@ -66,31 +58,28 @@ module MojoMagick
|
|
66
58
|
end
|
67
59
|
|
68
60
|
# Generic commands. Arguments will be formatted if necessary
|
61
|
+
# rubocop:disable Lint/MissingSuper, Style/MissingRespondToMissing
|
69
62
|
def method_missing(command, *args)
|
70
|
-
if command.to_s[-1, 1] ==
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
add_formatted arg
|
77
|
-
end
|
63
|
+
@opts << if command.to_s[-1, 1] == "!"
|
64
|
+
"+#{command.to_s.chop}"
|
65
|
+
else
|
66
|
+
"-#{command}"
|
67
|
+
end
|
68
|
+
@opts << args
|
78
69
|
self
|
79
70
|
end
|
80
71
|
|
81
|
-
|
82
|
-
|
72
|
+
# rubocop:enable Lint/MissingSuper, Style/MissingRespondToMissing
|
73
|
+
def to_a
|
74
|
+
@opts.flatten
|
83
75
|
end
|
84
76
|
|
85
77
|
protected
|
86
|
-
def add_formatted(arg)
|
87
|
-
# Quote anything that would cause problems on *nix or windows
|
88
|
-
@opts << quoted_arg(arg)
|
89
|
-
end
|
90
78
|
|
91
79
|
def quoted_arg(arg)
|
92
|
-
return arg unless
|
93
|
-
|
80
|
+
return arg unless /[#'<>^|&();` ]/.match?(arg)
|
81
|
+
|
82
|
+
['"', arg.gsub('"', '\"').tr("'", "\'"), '"'].join
|
94
83
|
end
|
95
84
|
end
|
96
85
|
end
|