le_meme 0.0.8 → 0.1.0
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/.rubocop.yml +5 -0
- data/README.md +9 -9
- data/Rakefile +1 -1
- data/bin/console +14 -0
- data/bin/meme +68 -39
- data/le_meme.gemspec +9 -7
- data/lib/le_meme.rb +5 -179
- data/lib/le_meme/meme.rb +107 -0
- data/lib/le_meme/meme_lib.rb +49 -0
- data/lib/le_meme/version.rb +3 -2
- data/spec/le_meme/meme_spec.rb +30 -0
- data/spec/spec_helper.rb +1 -3
- metadata +40 -35
- data/spec/le_meme_spec.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 855301d19080a1db16d6df06321f7dbfcc8bb712
|
4
|
+
data.tar.gz: 0596203d86d7345eda25226571cec379b6b12ac0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0bdd71930c20ecd3b8aa3bea58e193db74e4eeddd69c2a3af25db7111e3e1538163b52919be9bc9c0a0dd95d7c0ce7014ca4353224fdc43fe6f707062366e891
|
7
|
+
data.tar.gz: de6f304bbaeca7733b128b49717b9e012af2d062b9a0d7d3d06760a04a0302f444846f58afcaba4a5967fa4dee60ebc87e66d4a4a6128093b23043763d2cf550
|
data/.rubocop.yml
ADDED
data/README.md
CHANGED
@@ -37,15 +37,20 @@ Should be straightforward enough for even the dankest memer.
|
|
37
37
|
### I want to make memes in my ruby application
|
38
38
|
Easy enough!
|
39
39
|
|
40
|
+
#### Using a template
|
40
41
|
```ruby
|
41
42
|
require 'le_meme'
|
43
|
+
LeMeme::MemeLib.new_with_default_memes.meme(template: 'maymay', top: 'Top text', bottom: 'Bottom Text').to_file
|
44
|
+
```
|
42
45
|
|
43
|
-
|
46
|
+
#### Using any old image
|
44
47
|
|
45
|
-
|
48
|
+
```ruby
|
49
|
+
require 'le_meme'
|
50
|
+
LeMeme::Meme.new('~/Desktop/to_be_memed.jpg', top: 'Top text', bottom: 'bottom text').to_file
|
46
51
|
```
|
47
52
|
|
48
|
-
See the [docs](http://www.rubydoc.info/gems/le_meme) for all the
|
53
|
+
See the [docs](http://www.rubydoc.info/gems/le_meme) for all the memetastic details
|
49
54
|
|
50
55
|
## Contributing
|
51
56
|
|
@@ -60,12 +65,7 @@ Because the world needs more dank memes!
|
|
60
65
|
|
61
66
|
Actually, because I wanted to take some time and clean up the core of [memebot](http://github.com/paradox460/memebot), and figured making the essential meme generation a gem was the best way to do it. Now I can spam my coworkers with memes in hipchat as well.
|
62
67
|
|
63
|
-
##
|
64
|
-
- [ ] Allow dynamic additon of memes to internal memecache
|
65
|
-
- [ ] Test `meme` binary
|
66
|
-
|
67
|
-
## License
|
68
|
-
|
68
|
+
## LICENSE
|
69
69
|
```
|
70
70
|
Copyright (c) 2015 Jeff Sandberg
|
71
71
|
|
data/Rakefile
CHANGED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'le_meme'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
Pry.start
|
data/bin/meme
CHANGED
@@ -1,61 +1,90 @@
|
|
1
|
-
#!/
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'bundler/setup'
|
3
4
|
require 'le_meme'
|
4
|
-
require '
|
5
|
+
require 'optparse'
|
6
|
+
require 'pry'
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
if ARGV.empty?
|
11
|
+
exit(0) unless $stdout.tty?
|
12
|
+
$stdout.puts 'Usage: meme [options] [top-text] [bottom-text]'
|
13
|
+
$stdout.puts ' run -h for help'
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
memelib = LeMeme::MemeLib.new_with_default_memes
|
18
|
+
|
19
|
+
OptionParser.new do |opts|
|
20
|
+
opts.banner = <<-HEREDOC
|
9
21
|
Usage: meme [options] [top-text] [bottom-text]
|
10
22
|
|
11
23
|
Top and bottom may be set by the last 2 unprefixed arguments, or by their respective flags.
|
12
24
|
If you don't specify a meme, either via image path or by template, we'll pick one for you.
|
13
25
|
HEREDOC
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
on
|
18
|
-
|
19
|
-
on 'p', 'path=', 'path to an image to use for the meme'
|
20
|
-
on 'w', 'watermark=', 'text to watermark the image'
|
21
|
-
on 'o', 'outpath=', 'path to save the final meme to'
|
22
|
-
on 'O', 'open', "Open the file in your system's default image viewer"
|
23
|
-
|
24
|
-
on 'templates', 'list all the meme templates' do
|
25
|
-
puts memegen.memes.keys.join("\n")
|
26
|
-
exit
|
26
|
+
|
27
|
+
opts.separator '---'
|
28
|
+
|
29
|
+
opts.on('-tTOP', '--top=TOP', 'top text') do |top|
|
30
|
+
options[:top] = top
|
27
31
|
end
|
28
|
-
on
|
29
|
-
|
30
|
-
|
32
|
+
opts.on('-bBOTTOM', '--bottom=BOTTOM', 'bottom text') do |bottom|
|
33
|
+
options[:bottom] = bottom
|
34
|
+
end
|
35
|
+
opts.on('-wWATERMARK', '--watermark=WATERMARK', 'watermark text') do |watermark|
|
36
|
+
options[:watermark] = watermark
|
31
37
|
end
|
32
|
-
end
|
33
|
-
if ARGV.empty?
|
34
|
-
$stdout.puts opts if $stdout.tty?
|
35
|
-
exit(0)
|
36
|
-
end
|
37
38
|
|
38
|
-
opts.
|
39
|
+
opts.separator ''
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
opts.on('-mTEMPLATE', '--meme=TEMPLATE', 'meme template you want to use. See --templates for list') do |template|
|
42
|
+
options[:template] = template
|
43
|
+
end
|
42
44
|
|
45
|
+
opts.on('-pPATH', '--path=PATH', 'path to image file to use') do |path|
|
46
|
+
options[:path] = path
|
47
|
+
end
|
48
|
+
opts.on('-oPATH', '--outpath=PATH', 'path to save generated meme') do |path|
|
49
|
+
options[:outpath] = path
|
50
|
+
end
|
51
|
+
opts.on('-O', '--open', 'open when finished') do |o|
|
52
|
+
options[:open] = o
|
53
|
+
end
|
43
54
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
55
|
+
opts.separator ''
|
56
|
+
|
57
|
+
opts.on('--templates', 'list all the meme templates') do
|
58
|
+
puts memelib.memes.keys.join("\n")
|
59
|
+
exit(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on('-h', '--help', 'prints this help') do
|
63
|
+
puts opts
|
64
|
+
exit(0)
|
65
|
+
end
|
66
|
+
end.parse!
|
67
|
+
|
68
|
+
top = options[:top] || ARGV[0]
|
69
|
+
bottom = options[:bottom] || ARGV[1]
|
70
|
+
|
71
|
+
meme = if options[:path]
|
72
|
+
LeMeme::Meme.new(options[:path], top: top, bottom: bottom, watermark: options[:watermark])
|
73
|
+
else
|
74
|
+
template = options[:template].nil? ? nil : options[:template].strip
|
75
|
+
memelib.meme(template: template, top: top, bottom: bottom, watermark: options[:watermark])
|
76
|
+
end
|
77
|
+
|
78
|
+
output = meme.to_file(options[:outpath]).path
|
50
79
|
|
51
|
-
if
|
80
|
+
if options[:open]
|
52
81
|
case RbConfig::CONFIG['host_os']
|
53
82
|
when /mswin|mingw|cygwin/
|
54
|
-
`start #{output}`
|
83
|
+
`start '#{output}'`
|
55
84
|
when /darwin/
|
56
|
-
`open -F #{output}`
|
85
|
+
`open -F '#{output}'`
|
57
86
|
when /linux|bsd/
|
58
|
-
`xdg-open #{output}`
|
87
|
+
`xdg-open '#{output}'`
|
59
88
|
else
|
60
89
|
$stderr.puts "Sorry, don't know how to open on your system! (#{RbConfig::Config['host_os']})"
|
61
90
|
end
|
data/le_meme.gemspec
CHANGED
@@ -16,14 +16,16 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.
|
22
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
-
spec.add_development_dependency 'pry', '~> 0.10'
|
24
|
-
spec.add_development_dependency 'rspec', '~> 3.1'
|
21
|
+
spec.required_ruby_version = '>= 2.1'
|
25
22
|
|
26
|
-
spec.
|
23
|
+
spec.add_development_dependency 'bundler'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'pry'
|
26
|
+
spec.add_development_dependency 'pry-byebug'
|
27
|
+
spec.add_development_dependency 'rspec'
|
28
|
+
|
29
|
+
spec.add_runtime_dependency 'rmagick', '~> 2.15'
|
27
30
|
spec.add_runtime_dependency 'word_wrapper', '~> 0.5.0'
|
28
|
-
spec.add_runtime_dependency 'slop', '~> 3.6'
|
29
31
|
end
|
data/lib/le_meme.rb
CHANGED
@@ -1,184 +1,10 @@
|
|
1
|
-
require 'le_meme/version'
|
2
1
|
require 'rmagick'
|
3
2
|
require 'word_wrapper'
|
4
|
-
require 'pathname'
|
5
|
-
|
6
|
-
# Create some dank memes
|
7
|
-
#
|
8
|
-
# @author Jeff Sandberg <paradox460@gmail.com>
|
9
|
-
class LeMeme
|
10
|
-
VALID_IMAGE_EXTENSIONS = /\.(jp[e]?g|png|gif)$/i
|
11
|
-
|
12
|
-
attr_reader :memes
|
13
|
-
|
14
|
-
# Create a new instance of LeMeme, for generating memes.
|
15
|
-
#
|
16
|
-
# @param [Array] Directories of images you want the gem to know about. Scrapes images in said directory
|
17
|
-
# @return [String] description of returned object
|
18
|
-
def initialize(*dirs)
|
19
|
-
@memes = {}
|
20
|
-
load_directory!
|
21
|
-
dirs.each do |dir|
|
22
|
-
load_directory!(dir)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Create a meme with the given text
|
27
|
-
#
|
28
|
-
# @param path [String] The path to the image file you want to annotate.
|
29
|
-
# @param top [String] The text you want to appear on the top of the meme.
|
30
|
-
# @param bottom [String] The text you want to appear on the bottom of the meme.
|
31
|
-
# @param watermark [String] The watermark text. If nil it is omitted
|
32
|
-
# @param outpath [String] Where do you want to put the generated meme. Defaults to /tmp/meme-<timestamp>.<extension>
|
33
|
-
# @param nowrite [Boolean] Returns the meme as a blob (string) instead of writing to disk.
|
34
|
-
# @return [String] Either the generated meme object (if nowrite) or a path to the meme
|
35
|
-
def generate(path:, top: nil, bottom: nil, watermark: nil, outpath: nil, nowrite: false)
|
36
|
-
top = (top || '').upcase
|
37
|
-
bottom = (bottom || '').upcase
|
38
|
-
|
39
|
-
path = Pathname.new(path).realpath
|
40
|
-
|
41
|
-
canvas = Magick::ImageList.new(path)
|
42
|
-
|
43
|
-
caption_meme(top, Magick::NorthGravity, canvas) unless top.empty?
|
44
|
-
caption_meme(bottom, Magick::SouthGravity, canvas) unless bottom.empty?
|
45
|
-
|
46
|
-
# Draw the watermark
|
47
|
-
unless watermark.nil?
|
48
|
-
watermark_draw = Magick::Draw.new
|
49
|
-
watermark_draw.annotate(canvas, 0, 0, 0, 0, " #{watermark}") do
|
50
|
-
self.font = 'Helvetica'
|
51
|
-
self.fill = 'white'
|
52
|
-
self.text_antialias(false)
|
53
|
-
self.font_weight = 100
|
54
|
-
self.gravity = Magick::SouthEastGravity
|
55
|
-
self.pointsize = 10
|
56
|
-
self.undercolor = 'hsla(0,0,0,.5)'
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
output_path = outpath || "/tmp/meme-#{Time.now.to_i}#{path.extname}"
|
61
|
-
if nowrite
|
62
|
-
canvas.to_blob
|
63
|
-
else
|
64
|
-
canvas.write(output_path)
|
65
|
-
output_path
|
66
|
-
end
|
67
|
-
end
|
68
|
-
alias_method :meme, :generate
|
69
3
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
# @param name [String] Name of the template to use. See #memes
|
74
|
-
# @param top [String] The text you want to appear on the top of the meme.
|
75
|
-
# @param bottom [String] The text you want to appear on the bottom of the meme.
|
76
|
-
# @param watermark [String] The watermark text. If nil it is omitted
|
77
|
-
# @param outpath [String] Where do you want to put the generated meme. Defaults to /tmp/meme-<timestamp>.<extension>
|
78
|
-
# @param nowrite [Boolean] Returns the meme as a blob (string) instead of writing to disk.
|
79
|
-
# @return [String] Either the generated meme object (if nowrite) or a path to the meme
|
80
|
-
def fast_meme(name: nil, top: nil, bottom: nil, watermark: nil, outpath: nil, nowrite: false)
|
81
|
-
if name.nil?
|
82
|
-
path = @memes[@memes.keys.sample]
|
83
|
-
elsif @memes[name].nil?
|
84
|
-
fail ArgumentError, "#{name} is not a pre-loaded meme"
|
85
|
-
else
|
86
|
-
path = @memes[name]
|
87
|
-
end
|
88
|
-
generate(path: path, top: top, bottom: bottom, watermark: watermark, outpath: outpath, nowrite: nowrite)
|
89
|
-
end
|
90
|
-
alias_method :m, :fast_meme
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
# Load all images in a directory as memes.
|
95
|
-
# Last loaded directory overrides first, if there are name conflicts.
|
96
|
-
#
|
97
|
-
# @param directory [String] Directory to scrape.
|
98
|
-
def load_directory!(directory = nil)
|
99
|
-
directory = directory.nil? ? "#{File.join(File.dirname(File.expand_path(__FILE__)), '../memes')}/*.jpg" : "#{directory}/*"
|
100
|
-
paths = Dir.glob(directory).select do |path|
|
101
|
-
path =~ VALID_IMAGE_EXTENSIONS
|
102
|
-
end
|
103
|
-
@memes.merge!(paths.reduce({}) do |images, path|
|
104
|
-
path = Pathname.new(path).realpath
|
105
|
-
name = path.split.last.sub(VALID_IMAGE_EXTENSIONS, '').to_s
|
106
|
-
images.merge(name => path)
|
107
|
-
end)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Caption a canvas
|
111
|
-
#
|
112
|
-
# @param text [String] The text to put on as a caption
|
113
|
-
# @param gravity [Grav] RMagick Gravity Constant
|
114
|
-
# @param canvas [Magick::ImageList] ImageList/Canvas to compose the text onto
|
115
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
116
|
-
def caption_meme(text, gravity, canvas)
|
117
|
-
text.gsub!('\\', '\\\\\\')
|
118
|
-
|
119
|
-
# 28 is the biggest pointsize memes dont look like shit on
|
120
|
-
min_pointsize = 28
|
121
|
-
max_pointsize = 128
|
122
|
-
current_pointsize = min_pointsize
|
123
|
-
current_stroke = current_pointsize / 30.0
|
124
|
-
|
125
|
-
max_width = canvas.columns - 20
|
126
|
-
max_height = (canvas.rows / 2) - 20
|
127
|
-
|
128
|
-
draw = Magick::Draw.new
|
129
|
-
draw.font = File.join(File.dirname(File.expand_path(__FILE__)), '..', 'fonts', 'Impact.ttf')
|
130
|
-
draw.font_weight = Magick::BoldWeight
|
131
|
-
metrics = nil
|
132
|
-
|
133
|
-
# Non-ragged word-wrap
|
134
|
-
text = word_wrap(text)
|
135
|
-
|
136
|
-
# Calculate out the largest pointsize that will fit
|
137
|
-
loop do
|
138
|
-
draw.pointsize = current_pointsize
|
139
|
-
last_metrics = metrics
|
140
|
-
metrics = draw.get_multiline_type_metrics(text)
|
141
|
-
|
142
|
-
if metrics.width + current_stroke > max_width ||
|
143
|
-
metrics.height + current_stroke > max_height ||
|
144
|
-
current_pointsize > max_pointsize
|
145
|
-
if current_pointsize > min_pointsize
|
146
|
-
current_pointsize -= 1
|
147
|
-
current_stroke = current_pointsize / 30.0
|
148
|
-
metrics = last_metrics
|
149
|
-
end
|
150
|
-
break
|
151
|
-
else
|
152
|
-
current_pointsize += 1
|
153
|
-
current_stroke = current_pointsize / 30.0
|
154
|
-
end
|
155
|
-
end
|
156
|
-
# rubocop:disable Style/RedundantSelf
|
157
|
-
draw.annotate(canvas, canvas.columns, canvas.rows - 10, 0, 0, text) do
|
158
|
-
self.interline_spacing = -(current_pointsize / 5)
|
159
|
-
self.stroke_antialias(true)
|
160
|
-
self.stroke = 'black'
|
161
|
-
self.fill = 'white'
|
162
|
-
self.gravity = gravity
|
163
|
-
self.stroke_width = current_stroke
|
164
|
-
self.pointsize = current_pointsize
|
165
|
-
end
|
166
|
-
# rubocop:enable Style/RedundantSelf
|
167
|
-
end
|
168
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
4
|
+
require 'le_meme/version'
|
5
|
+
require 'le_meme/meme'
|
6
|
+
require 'le_meme/meme_lib'
|
169
7
|
|
170
|
-
|
171
|
-
|
172
|
-
#
|
173
|
-
# @param text [String] The text string to word-wrap.
|
174
|
-
# @param col [Fixnum] The number of "columns" to wrap at
|
175
|
-
# @return [String] The text, with the
|
176
|
-
def word_wrap(text, col: 24)
|
177
|
-
text.strip.gsub(/\n\r/, '\s')
|
178
|
-
text = WordWrapper::MinimumRaggedness.new(col, text).wrap
|
179
|
-
text = text.split("\n").map do |line|
|
180
|
-
line.length > col ? line.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n").strip : line
|
181
|
-
end * "\n"
|
182
|
-
text.strip
|
183
|
-
end
|
8
|
+
module LeMeme
|
9
|
+
IMAGE_EXTENSIONS = /\.(jp[e]?g|png|gif)$/i
|
184
10
|
end
|
data/lib/le_meme/meme.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
module LeMeme
|
2
|
+
# A single meme object
|
3
|
+
class Meme
|
4
|
+
attr_accessor(*%i(path top bottom watermark))
|
5
|
+
# @param [String, Pathanem] path Path to an image for the meme background
|
6
|
+
# @param [String] top: nil The text on the top of the meme
|
7
|
+
# @param [String] bottom: nil The text on the bottom of the meme
|
8
|
+
# @param [String] watermark: nil Watermark text
|
9
|
+
# @return [Meme] A new meme object
|
10
|
+
def initialize(path, top: nil, bottom: nil, watermark: nil)
|
11
|
+
@path = File.expand_path(path)
|
12
|
+
@top = top.to_s.upcase
|
13
|
+
@bottom = bottom.to_s.upcase
|
14
|
+
@watermark = watermark
|
15
|
+
@canvas = Magick::ImageList.new(@path)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Outputs the meme to the file system
|
19
|
+
#
|
20
|
+
# @param [String] path = nil Where to save the meme
|
21
|
+
# @return [File] File object representing the meme
|
22
|
+
def to_file(path = nil)
|
23
|
+
path = File.expand_path(path.nil? ? "#{ENV['TMPDIR']}meme-#{Time.now.to_i}.jpg" : path)
|
24
|
+
generate!
|
25
|
+
|
26
|
+
file = File.new(path, 'w+')
|
27
|
+
@canvas.write(path)
|
28
|
+
|
29
|
+
file
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get a binary string representing the meme
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
def to_blob
|
36
|
+
generate!
|
37
|
+
|
38
|
+
@canvas.to_blob
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def generate!
|
44
|
+
return if @generated
|
45
|
+
@generated = true
|
46
|
+
caption(@top, Magick::NorthGravity) unless @top.empty?
|
47
|
+
caption(@bottom, Magick::SouthGravity) unless @bottom.empty?
|
48
|
+
watermark unless watermark.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# rubocop:disable Metrics/MethodLength
|
52
|
+
def caption(text, gravity)
|
53
|
+
text = word_wrap(text)
|
54
|
+
draw, pointsize = calculate_pointsize(text)
|
55
|
+
|
56
|
+
draw.annotate(@canvas, @canvas.columns, @canvas.rows - 10, 0, 0, text) do
|
57
|
+
self.interline_spacing = -(pointsize / 5)
|
58
|
+
stroke_antialias(true)
|
59
|
+
self.stroke = 'black'
|
60
|
+
self.fill = 'white'
|
61
|
+
self.gravity = gravity
|
62
|
+
self.stroke_width = pointsize / 30.0
|
63
|
+
self.pointsize = pointsize
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rubocop:enable Metrics/MethodLength
|
67
|
+
|
68
|
+
def watermark
|
69
|
+
draw = Magick::Draw.new
|
70
|
+
draw.annotate(@canvas, 0, 0, 0, 0, " #{@watermark}") do
|
71
|
+
self.fill = 'white'
|
72
|
+
text_antialias(false)
|
73
|
+
self.font_weight = 100
|
74
|
+
self.gravity = Magick::SouthEastGravity
|
75
|
+
self.pointsize = 10
|
76
|
+
self.undercolor = 'hsla(0,0,0,.5)'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# rubocop:disable Metrics/AbcSize
|
81
|
+
def calculate_pointsize(text, size_range: 28...128)
|
82
|
+
draw = Magick::Draw.new
|
83
|
+
draw.font = File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'fonts', 'Impact.ttf')
|
84
|
+
draw.font_weight = Magick::BoldWeight
|
85
|
+
|
86
|
+
size = size_range.detect(-> { size_range.last }) do |pointsize|
|
87
|
+
draw.pointsize = pointsize + 1
|
88
|
+
current_stroke = pointsize / 30.0
|
89
|
+
|
90
|
+
metrics = draw.get_multiline_type_metrics(text)
|
91
|
+
|
92
|
+
metrics.width + current_stroke > @canvas.columns - 20 || metrics.height + current_stroke > (@canvas.rows / 2) - 20
|
93
|
+
end
|
94
|
+
[draw, size]
|
95
|
+
end
|
96
|
+
# rubocop:enable Metrics/AbcSize
|
97
|
+
|
98
|
+
def word_wrap(text, col: 24)
|
99
|
+
text.strip.gsub(/\n\r/, '\s')
|
100
|
+
text = WordWrapper::MinimumRaggedness.new(col, text).wrap
|
101
|
+
text = text.split("\n").map do |line|
|
102
|
+
line.length > col ? line.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n").strip : line
|
103
|
+
end * "\n"
|
104
|
+
text.strip
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module LeMeme
|
2
|
+
# Utility for easily generating memes based off templates
|
3
|
+
class MemeLib
|
4
|
+
attr_reader :memes
|
5
|
+
# Creates a meme library
|
6
|
+
#
|
7
|
+
# @param [String] *dirs Directory glob patterns to meme templates
|
8
|
+
# @return [MemeLib]
|
9
|
+
def initialize(*dirs)
|
10
|
+
@memes = {}
|
11
|
+
dirs.each(&method(:load_directory!))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a meme library, preloaded with the included templates
|
15
|
+
#
|
16
|
+
# @return [MemeLib]
|
17
|
+
def self.new_with_default_memes
|
18
|
+
path = File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'memes', '*')
|
19
|
+
new(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Loads a directory into the MemeLib, for template consumption
|
23
|
+
# Clobbers any existing templates
|
24
|
+
#
|
25
|
+
# @param [String] dir Directory glob pattern to meme templates
|
26
|
+
# @return [Hash] Hash of all templates and their filepaths
|
27
|
+
def load_directory!(dir)
|
28
|
+
paths = Dir.glob(dir).grep LeMeme::IMAGE_EXTENSIONS
|
29
|
+
@memes.merge!(paths.reduce({}) do |images, path|
|
30
|
+
path = File.expand_path(path)
|
31
|
+
name = path.split.last.sub(LeMeme::IMAGE_EXTENSIONS, '').to_s
|
32
|
+
images.merge(name => path)
|
33
|
+
end)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a meme from a template
|
37
|
+
#
|
38
|
+
# @param [String] template: nil The template to use. Omit for random template
|
39
|
+
# @param [String] top: nil
|
40
|
+
# @param [String] bottom: nil
|
41
|
+
# @param [String] watermark: nil
|
42
|
+
# @return [LeMeme::Meme]
|
43
|
+
def meme(template: nil, top: nil, bottom: nil, watermark: nil)
|
44
|
+
path = template.nil? ? @memes.values.sample : @memes[template]
|
45
|
+
|
46
|
+
Meme.new(path, top: top, bottom: bottom, watermark: watermark)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/le_meme/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe LeMeme::Meme do
|
4
|
+
subject { LeMeme::Meme.new('memes/maymay.jpg') }
|
5
|
+
describe :new do
|
6
|
+
context 'without text' do
|
7
|
+
it { is_expected.to be_a LeMeme::Meme }
|
8
|
+
it { expect(subject.path).to match(%r{memes/maymay.jpg$}) }
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'with text' do
|
12
|
+
subject do
|
13
|
+
LeMeme::Meme.new('memes/maymay.jpg', top: 'top text', bottom: 'bottom text')
|
14
|
+
end
|
15
|
+
|
16
|
+
it { expect(subject.top).to eql('TOP TEXT') }
|
17
|
+
it { expect(subject.bottom).to eql('BOTTOM TEXT') }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe :to_file do
|
22
|
+
it { expect(subject.to_file).to be_a(File) }
|
23
|
+
it { expect(subject.to_file.path).to match(/#{ENV['TMPDIR']}meme-\d+.jpg/) }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe :to_blob do
|
27
|
+
it { expect(subject.to_blob).to be_a(String) }
|
28
|
+
it { expect(subject.to_blob.size).to eql(43_685) }
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,131 +1,136 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: le_meme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Sandberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: pry-byebug
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
76
|
-
type: :
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rmagick
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: '2.15'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: '2.15'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: word_wrapper
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 0.5.0
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
110
|
+
version: 0.5.0
|
111
111
|
description: A gem that generates memes. Can be used as a library or command-line
|
112
112
|
executable.
|
113
113
|
email:
|
114
114
|
- paradox460@gmail.com
|
115
115
|
executables:
|
116
|
+
- console
|
116
117
|
- meme
|
117
118
|
extensions: []
|
118
119
|
extra_rdoc_files: []
|
119
120
|
files:
|
120
121
|
- ".gitignore"
|
122
|
+
- ".rubocop.yml"
|
121
123
|
- Gemfile
|
122
124
|
- LICENSE.txt
|
123
125
|
- README.md
|
124
126
|
- Rakefile
|
127
|
+
- bin/console
|
125
128
|
- bin/meme
|
126
129
|
- fonts/Impact.ttf
|
127
130
|
- le_meme.gemspec
|
128
131
|
- lib/le_meme.rb
|
132
|
+
- lib/le_meme/meme.rb
|
133
|
+
- lib/le_meme/meme_lib.rb
|
129
134
|
- lib/le_meme/version.rb
|
130
135
|
- memes/10guy.jpg
|
131
136
|
- memes/aaduck.jpg
|
@@ -208,7 +213,7 @@ files:
|
|
208
213
|
- memes/wonka.jpg
|
209
214
|
- memes/yee.jpg
|
210
215
|
- memes/yuno.jpg
|
211
|
-
- spec/
|
216
|
+
- spec/le_meme/meme_spec.rb
|
212
217
|
- spec/spec_helper.rb
|
213
218
|
homepage: http://github.com/paradox460/le_meme
|
214
219
|
licenses:
|
@@ -222,7 +227,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
222
227
|
requirements:
|
223
228
|
- - ">="
|
224
229
|
- !ruby/object:Gem::Version
|
225
|
-
version: '
|
230
|
+
version: '2.1'
|
226
231
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
232
|
requirements:
|
228
233
|
- - ">="
|
@@ -230,10 +235,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
235
|
version: '0'
|
231
236
|
requirements: []
|
232
237
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.
|
238
|
+
rubygems_version: 2.5.1
|
234
239
|
signing_key:
|
235
240
|
specification_version: 4
|
236
241
|
summary: Dank memes, in gem form
|
237
242
|
test_files:
|
238
|
-
- spec/
|
243
|
+
- spec/le_meme/meme_spec.rb
|
239
244
|
- spec/spec_helper.rb
|
data/spec/le_meme_spec.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe LeMeme do
|
4
|
-
let(:meme) { LeMeme.new }
|
5
|
-
|
6
|
-
describe '#generate' do
|
7
|
-
let(:image) { File.join(File.dirname(File.expand_path(__FILE__)), '../memes/maymay.jpg') }
|
8
|
-
context 'without an image path' do
|
9
|
-
it 'should raise ArgumentError with "missing keyword: path"' do
|
10
|
-
expect { meme.generate }.to raise_error(ArgumentError, 'missing keyword: path')
|
11
|
-
end
|
12
|
-
end
|
13
|
-
context 'with an image path' do
|
14
|
-
x = ['text', nil] * 3
|
15
|
-
x.permutation(3).to_a.uniq.each do |top, bottom, watermark|
|
16
|
-
it "should generate a meme with top: '#{top}', bottom: '#{bottom}', and watermark: '#{watermark}'" do
|
17
|
-
expect_any_instance_of(Magick::ImageList).to receive(:write).and_return("/tmp/meme-#{Time.now.to_i}.jpg")
|
18
|
-
expect(meme.generate(path: image, top: top, bottom: bottom, watermark: watermark)).to match(%r{/tmp/meme-\d+.jpg})
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
context 'with an outpath' do
|
23
|
-
it 'should generate a meme at the specified outpath' do
|
24
|
-
expect_any_instance_of(Magick::ImageList).to receive(:write).and_return('test.jpg')
|
25
|
-
expect(meme.generate(path: image, top: 'top', bottom: 'bottom', outpath: 'test.jpg')).to eq('test.jpg')
|
26
|
-
end
|
27
|
-
end
|
28
|
-
context 'with nowrite set to true' do
|
29
|
-
it 'should output the meme as a blob string' do
|
30
|
-
mymeme = meme.generate(path: image, nowrite: true)
|
31
|
-
expect(mymeme.size).to eq(43618)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe '#meme' do
|
37
|
-
it 'should be an alias of #generate' do
|
38
|
-
expect(meme.method(:meme)).to eq(meme.method(:generate))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe '#fast_meme' do
|
43
|
-
let(:template) { 'maymay' }
|
44
|
-
it 'should pass its params to generate_meme' do
|
45
|
-
expect(meme).to receive(:generate) do |args|
|
46
|
-
expect(args[:path]).to eq(Pathname.new('/Users/jeffsandberg/Developer/le_meme/memes/maymay.jpg'))
|
47
|
-
expect(args[:top]).to eq('top text')
|
48
|
-
expect(args[:bottom]).to eq('bottom text')
|
49
|
-
expect(args[:watermark]).to eq('watermark')
|
50
|
-
expect(args[:outpath]).to eq('outpath')
|
51
|
-
end
|
52
|
-
meme.fast_meme(name: template, top: 'top text', bottom: 'bottom text', watermark: 'watermark', outpath: 'outpath')
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'without a specified template' do
|
56
|
-
it 'should generate a meme' do
|
57
|
-
expect_any_instance_of(Magick::ImageList).to receive(:write).and_return("/tmp/meme-#{Time.now.to_i}.jpg")
|
58
|
-
expect(meme.fast_meme).to match(%r{/tmp/meme-\d+.jpg})
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe '#m' do
|
64
|
-
it 'should be an alias of #fast_meme' do
|
65
|
-
expect(meme.method(:m)).to eq(meme.method(:fast_meme))
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|