mojo_magick 0.5.7 → 0.6.5
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 +4 -4
- 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.yml +11 -0
- data/Gemfile +6 -1
- data/Gemfile.lock +48 -15
- data/README.md +40 -4
- data/Rakefile +15 -6
- data/examples/animated_gif.rb +11 -13
- data/examples/composite.rb +11 -13
- data/init.rb +1 -1
- data/lib/image_magick/fonts.rb +4 -11
- data/lib/mojo_magick.rb +93 -76
- data/lib/mojo_magick/command_status.rb +1 -1
- data/lib/mojo_magick/commands.rb +32 -0
- data/lib/mojo_magick/errors.rb +2 -0
- data/lib/mojo_magick/font.rb +1 -2
- data/lib/mojo_magick/opt_builder.rb +16 -27
- data/lib/mojo_magick/util/font_parser.rb +43 -0
- data/lib/mojo_magick/util/parser.rb +11 -56
- data/lib/mojo_magick/version.rb +1 -1
- data/mojo_magick.gemspec +19 -16
- data/test/fixtures/roll with it.jpg +0 -0
- data/test/{parser_test.rb → font_parser_test.rb} +7 -7
- data/test/font_test.rb +5 -5
- data/test/mojo_magick_test.rb +99 -88
- data/test/opt_builder_test.rb +99 -80
- data/test/test_helper.rb +7 -6
- metadata +47 -16
- data/lib/image_magick/resource_limits.rb +0 -91
- data/lib/initializers/hash.rb +0 -12
- data/test/fonts_test.rb +0 -11
- data/test/resource_limits_test.rb +0 -49
data/lib/mojo_magick.rb
CHANGED
@@ -1,15 +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'
|
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"
|
13
10
|
|
14
11
|
# MojoMagick is a stateless set of module methods which present a convient interface
|
15
12
|
# for accessing common tasks for ImageMagick command line library.
|
@@ -35,7 +32,8 @@ require 'tempfile'
|
|
35
32
|
#
|
36
33
|
# Equivalent to:
|
37
34
|
#
|
38
|
-
# 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')
|
39
37
|
#
|
40
38
|
# Example #mogrify usage:
|
41
39
|
#
|
@@ -43,7 +41,7 @@ require 'tempfile'
|
|
43
41
|
#
|
44
42
|
# Equivalent to:
|
45
43
|
#
|
46
|
-
# MojoMagick::raw_command('mogrify', '-shave 10x10 image.jpg')
|
44
|
+
# MojoMagick::Commands.raw_command('mogrify', '-shave 10x10 image.jpg')
|
47
45
|
#
|
48
46
|
# Example showing some additional options:
|
49
47
|
#
|
@@ -60,39 +58,40 @@ require 'tempfile'
|
|
60
58
|
# bang (!) can be appended to command names to use the '+' versions
|
61
59
|
# instead of '-' versions.
|
62
60
|
#
|
63
|
-
module MojoMagick
|
64
|
-
# enable resource limiting functionality
|
65
|
-
extend ImageMagick::ResourceLimits
|
66
|
-
extend ImageMagick::Fonts
|
67
61
|
|
68
|
-
|
69
|
-
|
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
|
70
68
|
end
|
71
69
|
|
72
|
-
|
73
|
-
|
74
|
-
|
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)
|
76
|
+
end
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
raise MojoError, "#{e.class}: #{e.message}"
|
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)
|
81
82
|
end
|
82
83
|
|
83
|
-
def
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
unless status.success?
|
88
|
-
err_msg = options[:err_msg] || "MojoMagick command failed: #{command}."
|
89
|
-
raise(MojoFailed, "#{err_msg} (Exit status: #{status.exit_code})\n Command: #{status.command}\n Error: #{status.error}")
|
90
|
-
end
|
91
|
-
status.return_value
|
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)
|
92
88
|
end
|
89
|
+
end
|
93
90
|
|
94
|
-
|
95
|
-
|
91
|
+
module MojoMagick
|
92
|
+
extend MojoMagickDeprecations
|
93
|
+
def self.windows?
|
94
|
+
!RUBY_PLATFORM.include(win32)
|
96
95
|
end
|
97
96
|
|
98
97
|
def self.shrink(source_file, dest_file, options)
|
@@ -110,46 +109,56 @@ module MojoMagick
|
|
110
109
|
# resizes an image and returns the filename written to
|
111
110
|
# options:
|
112
111
|
# :width / :height => scale to these dimensions
|
113
|
-
# :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)
|
114
114
|
# :percent => scale image to this percentage (do not specify :width/:height in this case)
|
115
115
|
def self.resize(source_file, dest_file, options)
|
116
|
-
scale_options =
|
117
|
-
|
118
|
-
scale_options << '<' unless options[:expand_only].nil?
|
119
|
-
scale_options << '!' unless options[:absolute_aspect].nil?
|
120
|
-
scale_options << '^' unless options[:fill].nil?
|
121
|
-
scale_options = scale_options.join
|
122
|
-
|
116
|
+
scale_options = extract_scale_options(options)
|
117
|
+
geometry = extract_geometry_options(options)
|
123
118
|
extras = []
|
124
|
-
if !options[:width].nil? && !options[:height].nil?
|
125
|
-
geometry = "#{options[:width]}X#{options[:height]}"
|
126
|
-
elsif !options[:percent].nil?
|
127
|
-
geometry = "#{options[:percent]}%"
|
128
|
-
else
|
129
|
-
raise MojoMagickError, "Unknown options for method resize: #{options.inspect}"
|
130
|
-
end
|
131
119
|
if !options[:fill].nil? && !options[:crop].nil?
|
132
|
-
extras <<
|
133
|
-
extras << "
|
120
|
+
extras << "-gravity"
|
121
|
+
extras << "Center"
|
122
|
+
extras << "-extent"
|
123
|
+
extras << geometry.to_s
|
134
124
|
end
|
135
|
-
raw_command(
|
125
|
+
Commands.raw_command("convert",
|
126
|
+
source_file,
|
127
|
+
"-resize", "#{geometry}#{scale_options}",
|
128
|
+
*extras, dest_file)
|
136
129
|
dest_file
|
137
130
|
end
|
138
131
|
|
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
|
+
|
139
148
|
def self.available_fonts
|
140
149
|
# returns width, height of image if available, nil if not
|
141
150
|
Font.all
|
142
151
|
end
|
143
152
|
|
144
153
|
def self.get_format(source_file, format_string)
|
145
|
-
raw_command(
|
154
|
+
Commands.raw_command("identify", "-format", format_string, source_file)
|
146
155
|
end
|
147
156
|
|
148
157
|
# returns an empty hash or a hash with :width and :height set (e.g. {:width => INT, :height => INT})
|
149
158
|
# raises MojoFailed when results are indeterminate (width and height could not be determined)
|
150
159
|
def self.get_image_size(source_file)
|
151
160
|
# returns width, height of image if available, nil if not
|
152
|
-
retval = get_format(source_file,
|
161
|
+
retval = get_format(source_file, "w:%w h:%h")
|
153
162
|
return {} unless retval
|
154
163
|
|
155
164
|
width = retval.match(/w:([0-9]+) /)
|
@@ -161,30 +170,38 @@ module MojoMagick
|
|
161
170
|
{ width: width, height: height }
|
162
171
|
end
|
163
172
|
|
164
|
-
def self.convert(source = nil, dest = nil)
|
165
|
-
opts = OptBuilder.new
|
166
|
-
opts.file source if source
|
167
|
-
yield opts
|
168
|
-
opts.file dest if dest
|
169
|
-
raw_command('convert', opts.to_s)
|
170
|
-
end
|
171
|
-
|
172
|
-
def self.mogrify(dest = nil)
|
173
|
-
opts = OptBuilder.new
|
174
|
-
yield opts
|
175
|
-
opts.file dest if dest
|
176
|
-
raw_command('mogrify', opts.to_s)
|
177
|
-
end
|
178
|
-
|
179
173
|
def self.tempfile(*opts)
|
180
174
|
data = opts[0]
|
181
175
|
rest = opts[1]
|
182
176
|
ext = rest && rest[:format]
|
183
|
-
file = Tempfile.new([
|
177
|
+
file = Tempfile.new(["mojo", ext ? ".#{ext}" : ""])
|
184
178
|
file.binmode
|
185
179
|
file.write(data)
|
186
180
|
file.path
|
187
181
|
ensure
|
188
182
|
file.close
|
189
183
|
end
|
190
|
-
|
184
|
+
|
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
|
196
|
+
end
|
197
|
+
|
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
|
@@ -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
@@ -7,7 +7,6 @@ module MojoMagick
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def initialize(property_hash = {})
|
10
|
-
property_hash.symbolize_keys!
|
11
10
|
%i[name family style stretch weight glyphs].each do |f|
|
12
11
|
setter = "#{f}="
|
13
12
|
send(setter, property_hash[f])
|
@@ -15,7 +14,7 @@ module MojoMagick
|
|
15
14
|
end
|
16
15
|
|
17
16
|
def self.all
|
18
|
-
ImageMagick::
|
17
|
+
ImageMagick::Fonts.all
|
19
18
|
end
|
20
19
|
end
|
21
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,21 +27,17 @@ 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
|
-
arguments.unshift
|
36
|
-
|
37
|
-
add_formatted arg
|
38
|
-
end
|
30
|
+
def annotate(*args, geometry: 0)
|
31
|
+
@opts << "-annotate"
|
32
|
+
arguments = [args.join]
|
33
|
+
arguments.unshift geometry.to_s
|
34
|
+
@opts << arguments
|
39
35
|
end
|
40
36
|
|
41
37
|
# Create a temporary file for the given image and add to command line
|
42
38
|
def format(*args)
|
43
|
-
@opts <<
|
44
|
-
|
45
|
-
add_formatted arg
|
46
|
-
end
|
39
|
+
@opts << "-format"
|
40
|
+
@opts << args
|
47
41
|
end
|
48
42
|
|
49
43
|
def blob(*args)
|
@@ -64,33 +58,28 @@ module MojoMagick
|
|
64
58
|
end
|
65
59
|
|
66
60
|
# Generic commands. Arguments will be formatted if necessary
|
61
|
+
# rubocop:disable Style/MissingRespondToMissing
|
67
62
|
def method_missing(command, *args)
|
68
|
-
@opts << if command.to_s[-1, 1] ==
|
63
|
+
@opts << if command.to_s[-1, 1] == "!"
|
69
64
|
"+#{command.to_s.chop}"
|
70
65
|
else
|
71
66
|
"-#{command}"
|
72
67
|
end
|
73
|
-
|
74
|
-
add_formatted arg
|
75
|
-
end
|
68
|
+
@opts << args
|
76
69
|
self
|
77
70
|
end
|
78
71
|
|
79
|
-
|
80
|
-
|
72
|
+
# rubocop:enable Style/MissingRespondToMissing
|
73
|
+
def to_a
|
74
|
+
@opts.flatten
|
81
75
|
end
|
82
76
|
|
83
77
|
protected
|
84
78
|
|
85
|
-
def add_formatted(arg)
|
86
|
-
# Quote anything that would cause problems on *nix or windows
|
87
|
-
@opts << quoted_arg(arg)
|
88
|
-
end
|
89
|
-
|
90
79
|
def quoted_arg(arg)
|
91
|
-
return arg unless
|
80
|
+
return arg unless /[#'<>^|&();` ]/.match?(arg)
|
92
81
|
|
93
|
-
['"', arg.gsub('"', '\"').
|
82
|
+
['"', arg.gsub('"', '\"').tr("'", "\'"), '"'].join
|
94
83
|
end
|
95
84
|
end
|
96
85
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# rubocop:disable Lint/AssignmentInCondition
|
2
|
+
module MojoMagick
|
3
|
+
module Util
|
4
|
+
class FontParser
|
5
|
+
attr_reader :raw_fonts
|
6
|
+
|
7
|
+
def initialize(raw_fonts)
|
8
|
+
@raw_fonts = raw_fonts
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
fonts = {}
|
13
|
+
enumerator = raw_fonts.split("\n").each
|
14
|
+
name = nil
|
15
|
+
while begin; line = enumerator.next; rescue StopIteration; line = nil; end
|
16
|
+
line.chomp!
|
17
|
+
line = enumerator.next if line_is_empty(line)
|
18
|
+
m = /^\s*Font:\s+(.*)$/.match(line)
|
19
|
+
if m
|
20
|
+
name = m[1].strip
|
21
|
+
fonts[name] = { name: name }
|
22
|
+
else
|
23
|
+
k, v = extract_key_value(line)
|
24
|
+
fonts[name][k] = v if k && name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
fonts.values.map { |f| MojoMagick::Font.new f }
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def extract_key_value(line)
|
33
|
+
key_val = line.split(":").map(&:strip)
|
34
|
+
[key_val[0].downcase.to_sym, key_val[1]]
|
35
|
+
end
|
36
|
+
|
37
|
+
def line_is_empty(line)
|
38
|
+
line.nil? || line.empty? || (/^\s+$/ =~ line)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# rubocop:enable Lint/AssignmentInCondition
|
@@ -1,65 +1,20 @@
|
|
1
|
+
require_relative "./font_parser"
|
1
2
|
module MojoMagick
|
2
3
|
module Util
|
3
4
|
class Parser
|
4
|
-
|
5
|
+
attr_reader :raw_fonts
|
5
6
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
while begin; line = enumerator.next; rescue StopIteration; line = nil; end
|
11
|
-
line.chomp!
|
12
|
-
line = enumerator.next if line.nil? || line.empty? || (/^\s+$/ =~ line)
|
13
|
-
if m = /^\s*Font:\s+(.*)$/.match(line)
|
14
|
-
name = m[1].strip
|
15
|
-
fonts[name] = {name: name}
|
16
|
-
else
|
17
|
-
key_val = line.split(/:/).map(&:strip)
|
18
|
-
k = key_val[0].downcase.to_sym
|
19
|
-
v = key_val[1]
|
20
|
-
fonts[name][k] = v if k && name
|
21
|
-
end
|
22
|
-
end
|
23
|
-
fonts.values.map { |f| MojoMagick::Font.new f}
|
7
|
+
def initialize
|
8
|
+
warn "DEPRECATION WARNING: This class has been deprecated and will be removed with"\
|
9
|
+
" the next minor version release."\
|
10
|
+
" Please use `MojoMagick::Util::FontParser` instead"
|
24
11
|
end
|
25
12
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
limits = data.strip.split
|
32
|
-
|
33
|
-
actual_values = {}
|
34
|
-
readable_values = {}
|
35
|
-
|
36
|
-
resources.each_index do |i|
|
37
|
-
resource = resources[i].downcase.to_sym
|
38
|
-
scale = limits[i].match(/[a-z]+$/) || []
|
39
|
-
value = limits[i].match(/^[0-9]+/)
|
40
|
-
unscaled_value = value ? value[0].to_i : -1
|
41
|
-
scaled_value = case scale[0]
|
42
|
-
when 'eb'
|
43
|
-
unscaled_value * (2**60)
|
44
|
-
when 'pb'
|
45
|
-
unscaled_value * (2**50)
|
46
|
-
when 'tb'
|
47
|
-
unscaled_value * (2**40)
|
48
|
-
when 'gb'
|
49
|
-
unscaled_value * (2**30)
|
50
|
-
when 'mb'
|
51
|
-
unscaled_value * (2**20)
|
52
|
-
when 'kb'
|
53
|
-
unscaled_value * (2**10)
|
54
|
-
when 'b'
|
55
|
-
unscaled_value
|
56
|
-
else
|
57
|
-
unscaled_value
|
58
|
-
end
|
59
|
-
actual_values[resource] = scaled_value
|
60
|
-
readable_values[resource] = limits[i]
|
61
|
-
end
|
62
|
-
[actual_values, readable_values]
|
13
|
+
def parse_fonts(fonts)
|
14
|
+
warn "DEPRECATION WARNING: #{__method__} has been deprecated and will be removed with"\
|
15
|
+
" the next minor version release."\
|
16
|
+
" Please use `MojoMagick::Util::FontParser#parse` instead"
|
17
|
+
FontParser.new(fonts).parse
|
63
18
|
end
|
64
19
|
end
|
65
20
|
end
|