magic_door 0.1.0b
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.
- data/LICENSE +20 -0
- data/README.rdoc +75 -0
- data/Rakefile +27 -0
- data/bin/magic-door +41 -0
- data/lib/magic_door.rb +220 -0
- data/magic_door.gemspec +20 -0
- metadata +81 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Daniel Mircea, OkapiStudio
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
= MagicDoor Generator
|
2
|
+
|
3
|
+
Generates custom button images with RMagick using the famous {sliding doors css tehnique}[http://www.alistapart.com/articles/slidingdoors].
|
4
|
+
|
5
|
+
|
6
|
+
== A brief introduction
|
7
|
+
|
8
|
+
While it can be argued that this functionality is being preceded by CSS 3, this code provides a happy crossbrowser experience, backed by the ability of using copyrighted fonts.
|
9
|
+
|
10
|
+
The script works by splitting a large wide image into two narrow ones that accomodate the length of a provided text. The location where the split ocurrs can be specified as an optional parameter.
|
11
|
+
|
12
|
+
|
13
|
+
== Generating buttons
|
14
|
+
|
15
|
+
Bellow you'll find a simple example of creating an image button:
|
16
|
+
|
17
|
+
options = {
|
18
|
+
:text => "Please click me"
|
19
|
+
:image => "wide-button-template.png"
|
20
|
+
:file_name => "click-me.png",
|
21
|
+
:css => "color: #333; font-size: 25"
|
22
|
+
}
|
23
|
+
|
24
|
+
file = MagicDoor.new(options)
|
25
|
+
file.save
|
26
|
+
|
27
|
+
You may set defaults in order to make subsequent calls simpler:
|
28
|
+
|
29
|
+
MagicDoor.defaults.merge!({
|
30
|
+
:source_path => "/project/path/buttons",
|
31
|
+
:destination_path => "/project/path/buttons/cache",
|
32
|
+
:split_at => 11,
|
33
|
+
:css => "padding: 10;
|
34
|
+
font-weight: bold;
|
35
|
+
font-size: 14;
|
36
|
+
color: #fff; text-align: center;
|
37
|
+
text-shadow: #cecefe 0 1;
|
38
|
+
font: '/project/path/fonts/MyriadPro-Semibold.otf"
|
39
|
+
})
|
40
|
+
|
41
|
+
|
42
|
+
== Limitations
|
43
|
+
|
44
|
+
All CSS sizes are calculated in pixels, so you should not add any measurement units after the numbers.
|
45
|
+
|
46
|
+
The padding property does not affect the top and bottom region.
|
47
|
+
|
48
|
+
Parsing the CSS and mapping it to RMagick is somewhat tendious, as such there are only a few css properties supported. Please consult the docs for a complete list.
|
49
|
+
|
50
|
+
|
51
|
+
== Command Line
|
52
|
+
|
53
|
+
You can run MagicDoor from the command line:
|
54
|
+
|
55
|
+
$ magic-door -t "hello world" -i "/home/daniel/Desktop/button.png" -f "/home/daniel/hello.png"
|
56
|
+
|
57
|
+
|
58
|
+
== Source code
|
59
|
+
|
60
|
+
The source code is hosted on Github: http://github.com/viseztrance/magic_door
|
61
|
+
|
62
|
+
To get MagicDoor from source:
|
63
|
+
|
64
|
+
git clone git@github.com:viseztrance/magic_door.git
|
65
|
+
|
66
|
+
|
67
|
+
== Acknowledgements
|
68
|
+
|
69
|
+
I would like to thank {OkapiStudio}[http://okapistudio.com] - my employer for allowing me to share this code.
|
70
|
+
|
71
|
+
|
72
|
+
== License
|
73
|
+
|
74
|
+
This package is licensed under the MIT license and/or the Creative
|
75
|
+
Commons Attribution-ShareAlike.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
|
3
|
+
|
4
|
+
spec = Gem::Specification.load(File.expand_path("magic_door.gemspec", File.dirname(__FILE__)))
|
5
|
+
|
6
|
+
# Create the documentation.
|
7
|
+
Rake::RDocTask.new do |rdoc|
|
8
|
+
rdoc.rdoc_files.include "README.rdoc", "lib/**/*.rb"
|
9
|
+
rdoc.options = spec.rdoc_options
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Push new release to rubyforge and git tag"
|
13
|
+
task :push do
|
14
|
+
sh "git push"
|
15
|
+
puts "Tagging version #{spec.version} .."
|
16
|
+
sh "git tag v#{spec.version}"
|
17
|
+
sh "git push --tag"
|
18
|
+
puts "Building and pushing gem .."
|
19
|
+
sh "gem build #{spec.name}.gemspec"
|
20
|
+
sh "gem push #{spec.name}-#{spec.version}.gem"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Install #{spec.name} locally"
|
24
|
+
task :install do
|
25
|
+
sh "gem build #{spec.name}.gemspec"
|
26
|
+
sh "gem install #{spec.name}-#{spec.version}.gem"
|
27
|
+
end
|
data/bin/magic-door
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "magic_door"
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
ARGV.options do |opt|
|
9
|
+
script_name = File.basename($0)
|
10
|
+
|
11
|
+
opt.set_summary_indent(" ")
|
12
|
+
opt.banner = "Usage: #{script_name} [options]"
|
13
|
+
opt.define_head "Generates custom button images with RMagick using the famous sliding doors css tehnique."
|
14
|
+
opt.separator ""
|
15
|
+
|
16
|
+
opt.on("-t", "--text=val", String,
|
17
|
+
"Text shown on the button, eg. \"click me\"") { |options[:text]| }
|
18
|
+
opt.on("-i", "--image=val", String,
|
19
|
+
"Path to the existing image that will be used as a template, eg. \"/home/me/rails/public/images/button.png\"") { |options[:image]| }
|
20
|
+
opt.on("-f", "--file=val", String,
|
21
|
+
"Path of the saved image, eg. \"/home/me/rails/public/images/cache/click-me.png\"") { |options[:file_name]| }
|
22
|
+
opt.on("-c", "--css=val", String,
|
23
|
+
"Css expression, eg. \"font-weight: bold; font-size: 25\"") { |options[:css]| }
|
24
|
+
opt.on("-s", "--split=val", String,
|
25
|
+
"The location where the template image be split in two.") { |options[:split_at]| }
|
26
|
+
|
27
|
+
opt.separator ""
|
28
|
+
|
29
|
+
opt.on_tail("-h", "--help", "Show this help message.") { puts opt; exit }
|
30
|
+
opt.on_tail("-v", "--version", "Show version") { puts script_name + ' ' + MagicDoor::VERSION; exit }
|
31
|
+
|
32
|
+
opt.parse!
|
33
|
+
end
|
34
|
+
|
35
|
+
if options[:text].nil? || options[:image].nil? || options[:file_name].nil?
|
36
|
+
puts %q|Insufficient arguments.
|
37
|
+
The "text" (-t), "image" (-i) and "file" (-f) options are required. -h for additional information.|
|
38
|
+
else
|
39
|
+
saved_file = MagicDoor.new(options).save
|
40
|
+
puts "Wrote #{saved_file.inspect}."
|
41
|
+
end
|
data/lib/magic_door.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
#
|
2
|
+
# = magic_door.rb - MagicDoor generator
|
3
|
+
#
|
4
|
+
# Author:: Daniel Mircea daniel@viseztrance.com
|
5
|
+
# Copyright:: Copyright (c) 2010 Daniel Mircea, OkapiStudio
|
6
|
+
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
7
|
+
|
8
|
+
require "RMagick"
|
9
|
+
require "fileutils"
|
10
|
+
|
11
|
+
class MagicDoor
|
12
|
+
|
13
|
+
VERSION = Gem::Specification.load(File.expand_path("../magic_door.gemspec", File.dirname(__FILE__))).version.to_s
|
14
|
+
|
15
|
+
module CssMethods #:nodoc:
|
16
|
+
|
17
|
+
def set_properties(css)
|
18
|
+
|
19
|
+
css.each do |args|
|
20
|
+
attribute = MagicDoor::CSS_ALIASES[:attributes][args.first] || args.first
|
21
|
+
value = proc {
|
22
|
+
v = MagicDoor::CSS_ALIASES[:values][args.last] || args.last
|
23
|
+
Float(v) rescue v
|
24
|
+
}.call
|
25
|
+
self.send("#{attribute}=", value)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
CSS_ALIASES = {
|
33
|
+
|
34
|
+
:attributes => {
|
35
|
+
"color" => "fill",
|
36
|
+
"font-weight" => "font_weight",
|
37
|
+
"font-family" => "font_family",
|
38
|
+
"font-size" => "pointsize",
|
39
|
+
"text-align" => "gravity",
|
40
|
+
},
|
41
|
+
|
42
|
+
:values => {
|
43
|
+
"bold" => Magick::BoldWeight,
|
44
|
+
"center" => Magick::CenterGravity,
|
45
|
+
"left" => Magick::WestGravity,
|
46
|
+
"right" => Magick::EastGravity
|
47
|
+
}
|
48
|
+
|
49
|
+
}
|
50
|
+
|
51
|
+
attr_reader :css,
|
52
|
+
:image
|
53
|
+
|
54
|
+
|
55
|
+
attr_accessor :text,
|
56
|
+
:split_at,
|
57
|
+
:source_path,
|
58
|
+
:destination_path,
|
59
|
+
:file_name
|
60
|
+
|
61
|
+
attr_accessor :canvas #:nodoc:
|
62
|
+
|
63
|
+
@@defaults = {
|
64
|
+
:css => "text-align: center; padding: 10; color: #000",
|
65
|
+
:split_at => 30,
|
66
|
+
:image => "button.png",
|
67
|
+
:source_path => "",
|
68
|
+
:destination_path => "",
|
69
|
+
:text => "Submit",
|
70
|
+
:file_name => "result.png"
|
71
|
+
}
|
72
|
+
|
73
|
+
class << self
|
74
|
+
|
75
|
+
# Sets +defaults+ for new object instances.
|
76
|
+
def defaults=(options)
|
77
|
+
@@defaults = options
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns defined defaults
|
81
|
+
def defaults
|
82
|
+
@@defaults
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
# Instantiate a new object.
|
88
|
+
# Missing options fallback to +defaults+.
|
89
|
+
# ==== Options:
|
90
|
+
# [*text*] Text that will be displayed inside the button.
|
91
|
+
# [*split_at*] Location where the image is being split.
|
92
|
+
# [*source_path*] Prefix for +image+ location.
|
93
|
+
# [*destination_path*] Prefix for +file_name+ location.
|
94
|
+
# [*file_name*] The name of the file being saved.
|
95
|
+
def initialize(options = {})
|
96
|
+
self.css = self.class.defaults[:css]
|
97
|
+
self.css = options[:css] if options[:css]
|
98
|
+
%w(source_path destination_path split_at image text file_name).each do |name|
|
99
|
+
n = name.to_sym
|
100
|
+
self.send("#{n}=", options[n] || self.class.defaults[n])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Creates the image file.
|
105
|
+
# ==== Returns
|
106
|
+
# Path of the saved file.
|
107
|
+
# ==== Options
|
108
|
+
# [*as*] Alternate way of setting the name of the file being saved. Fallsback to +file_name+.
|
109
|
+
def save(options = {})
|
110
|
+
compose_button
|
111
|
+
draw
|
112
|
+
write_file(options[:as] || file_name)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Sets the template image.
|
116
|
+
#
|
117
|
+
# Will fail if the image does not exist.
|
118
|
+
def image=(name)
|
119
|
+
@image = Magick::Image.read(File.join(source_path, name)).first
|
120
|
+
end
|
121
|
+
|
122
|
+
# Evaluates CSS expressions.
|
123
|
+
#
|
124
|
+
# Gracefuly merges the CSS defined in +defaults+.
|
125
|
+
#
|
126
|
+
# Sizes are being calculated in pixels. Don't specify any measuring units at all.
|
127
|
+
# ==== Available expressions:
|
128
|
+
# * color
|
129
|
+
# * font-weight
|
130
|
+
# * font-family
|
131
|
+
# * font-size
|
132
|
+
# * text-align
|
133
|
+
# * padding
|
134
|
+
# * width
|
135
|
+
# * height
|
136
|
+
# * left
|
137
|
+
# * right
|
138
|
+
def css=(expressions)
|
139
|
+
|
140
|
+
values = { :container => [], :shadow => [], :text => [] }
|
141
|
+
|
142
|
+
expressions.to_s.delete("\n").split(";").each do |expression|
|
143
|
+
|
144
|
+
args = expression.split(":").each { |arg| arg.strip! }
|
145
|
+
|
146
|
+
if %w(width height padding left right).include?(args.first)
|
147
|
+
values[:container] << args
|
148
|
+
elsif args.first == "text-shadow"
|
149
|
+
values[:shadow] << args
|
150
|
+
else
|
151
|
+
values[:text] << args
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
@css ||= {}
|
157
|
+
@css.merge!(values) { |key, old, new|
|
158
|
+
keys = new.collect { |e| e.first }
|
159
|
+
old.each { |e| new << e unless keys.index(e.first) }
|
160
|
+
new
|
161
|
+
}
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
def canvas #:nodoc:
|
166
|
+
@canvas ||= proc {
|
167
|
+
padding = (get_css_property(css[:container], "padding") || text_width).to_i
|
168
|
+
width = (get_css_property(css[:container], "width") || text_width).to_i + padding
|
169
|
+
height = (get_css_property(css[:container], "height") || image.rows).to_i
|
170
|
+
Magick::Image.new(width, height) { self.background_color = "none" }
|
171
|
+
}.call
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def draw
|
177
|
+
drawing = Magick::Draw.new
|
178
|
+
drawing.extend(CssMethods)
|
179
|
+
drawing.set_properties(css[:text])
|
180
|
+
margin_left = (get_css_property(css[:container], "left")).to_i
|
181
|
+
margin_right = (get_css_property(css[:container], "right")).to_i
|
182
|
+
margin = margin_left - margin_right
|
183
|
+
if !css[:shadow].empty?
|
184
|
+
args = css[:shadow].first.last.split
|
185
|
+
drawing.fill = args.first
|
186
|
+
drawing.annotate(self.canvas, 0, 0, margin + args[1].to_i, args[2].to_i, text)
|
187
|
+
drawing.fill = get_css_property(css[:text], "color")
|
188
|
+
end
|
189
|
+
drawing.annotate(self.canvas, 0, 0, margin, 0, text)
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_css_property(stack, key)
|
193
|
+
stack.reject { |c| c.first != key }.flatten.last
|
194
|
+
end
|
195
|
+
|
196
|
+
def compose_button
|
197
|
+
main_image = image.crop(Magick::EastGravity, (canvas.columns - split_at), canvas.rows)
|
198
|
+
left_image = image.crop(Magick::WestGravity, split_at, canvas.rows)
|
199
|
+
self.canvas.composite!(main_image, left_image.columns, 0, Magick::OverCompositeOp)
|
200
|
+
self.canvas.composite!(left_image, Magick::WestGravity, Magick::OverCompositeOp)
|
201
|
+
end
|
202
|
+
|
203
|
+
def write_file(name)
|
204
|
+
FileUtils.mkdir_p destination_path unless destination_path.empty?
|
205
|
+
save_path = File.join(destination_path, name)
|
206
|
+
canvas.write(save_path)
|
207
|
+
save_path
|
208
|
+
end
|
209
|
+
|
210
|
+
def text_width(offset = 6)
|
211
|
+
temporary_canvas = Magick::Image.new(1000, 150)
|
212
|
+
drawing = Magick::Draw.new
|
213
|
+
drawing.extend(CssMethods)
|
214
|
+
drawing.set_properties(css[:text])
|
215
|
+
|
216
|
+
return (drawing.get_type_metrics(temporary_canvas, text)["width"] + offset)
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
data/magic_door.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
spec = Gem::Specification.new do |spec|
|
2
|
+
spec.name = "magic_door"
|
3
|
+
spec.version = "0.1.0b"
|
4
|
+
spec.summary = "MagicDoor generator"
|
5
|
+
spec.description = <<-EOF
|
6
|
+
Generates custom button images with RMagick using the famous sliding doors css tehnique.
|
7
|
+
EOF
|
8
|
+
|
9
|
+
spec.authors << "Daniel Mircea"
|
10
|
+
spec.email = "daniel@viseztrance.com"
|
11
|
+
spec.homepage = "http://github.com/viseztrance/magic_door"
|
12
|
+
|
13
|
+
spec.files = Dir["{bin,lib,docs}/**/*"] + ["README.rdoc", "LICENSE", "Rakefile", "magic_door.gemspec"]
|
14
|
+
spec.executables = "magic-door"
|
15
|
+
|
16
|
+
spec.has_rdoc = true
|
17
|
+
spec.rdoc_options << "--main" << "README.rdoc" << "--title" << "MagicDoor generator" << "--line-numbers"
|
18
|
+
"--webcvs" << "http://github.com/viseztrance/magic_door"
|
19
|
+
spec.extra_rdoc_files = ["README.rdoc", "LICENSE"]
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: magic_door
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 3247241
|
5
|
+
prerelease: true
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0b
|
10
|
+
version: 0.1.0b
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Daniel Mircea
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-19 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |
|
23
|
+
Generates custom button images with RMagick using the famous sliding doors css tehnique.
|
24
|
+
|
25
|
+
email: daniel@viseztrance.com
|
26
|
+
executables:
|
27
|
+
- magic-door
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README.rdoc
|
32
|
+
- LICENSE
|
33
|
+
files:
|
34
|
+
- bin/magic-door
|
35
|
+
- lib/magic_door.rb
|
36
|
+
- README.rdoc
|
37
|
+
- LICENSE
|
38
|
+
- Rakefile
|
39
|
+
- magic_door.gemspec
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/viseztrance/magic_door
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --main
|
47
|
+
- README.rdoc
|
48
|
+
- --title
|
49
|
+
- MagicDoor generator
|
50
|
+
- --line-numbers
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 25
|
68
|
+
segments:
|
69
|
+
- 1
|
70
|
+
- 3
|
71
|
+
- 1
|
72
|
+
version: 1.3.1
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.7
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: MagicDoor generator
|
80
|
+
test_files: []
|
81
|
+
|