dragonfly 0.5.7 → 0.6.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.

Files changed (77) hide show
  1. data/.gitignore +1 -0
  2. data/.yardopts +1 -0
  3. data/History.md +109 -0
  4. data/VERSION +1 -1
  5. data/config.rb +1 -1
  6. data/dragonfly.gemspec +19 -16
  7. data/extra_docs/ActiveRecord.md +8 -7
  8. data/extra_docs/Analysers.md +1 -1
  9. data/extra_docs/Encoding.md +1 -1
  10. data/extra_docs/ExampleUseCases.md +66 -73
  11. data/extra_docs/GettingStarted.md +1 -1
  12. data/extra_docs/Processing.md +1 -1
  13. data/extra_docs/Shortcuts.md +2 -2
  14. data/extra_docs/UsingWithRails.md +25 -37
  15. data/features/rails_2.3.5.feature +1 -8
  16. data/features/rails_3.0.0.beta3.feature +7 -0
  17. data/features/steps/rails_steps.rb +1 -11
  18. data/features/support/env.rb +1 -1
  19. data/fixtures/files/app/views/albums/show.html.erb +1 -0
  20. data/fixtures/files/config/initializers/{aaa_dragonfly_load_path.rb → dragonfly.rb} +1 -0
  21. data/fixtures/rails_2.3.5/template.rb +4 -6
  22. data/fixtures/rails_3.0.0.beta3/template.rb +16 -0
  23. data/lib/dragonfly.rb +23 -1
  24. data/lib/dragonfly/active_record_extensions/attachment.rb +1 -1
  25. data/lib/dragonfly/analyser_list.rb +4 -0
  26. data/lib/dragonfly/analysis/base.rb +1 -0
  27. data/lib/dragonfly/analysis/r_magick_analyser.rb +7 -0
  28. data/lib/dragonfly/app.rb +3 -11
  29. data/lib/dragonfly/belongs_to_app.rb +24 -0
  30. data/lib/dragonfly/config/heroku_rails_images.rb +23 -0
  31. data/lib/dragonfly/config/r_magick_images.rb +69 -0
  32. data/lib/dragonfly/config/r_magick_text.rb +25 -0
  33. data/lib/dragonfly/config/rails_defaults.rb +18 -0
  34. data/lib/dragonfly/config/rails_images.rb +13 -0
  35. data/lib/dragonfly/configurable.rb +4 -3
  36. data/lib/dragonfly/data_storage/base.rb +2 -0
  37. data/lib/dragonfly/data_storage/s3data_store.rb +11 -4
  38. data/lib/dragonfly/delegator.rb +22 -10
  39. data/lib/dragonfly/encoder_list.rb +4 -0
  40. data/lib/dragonfly/encoding/base.rb +1 -0
  41. data/lib/dragonfly/encoding/r_magick_encoder.rb +52 -8
  42. data/lib/dragonfly/extended_temp_object.rb +26 -27
  43. data/lib/dragonfly/processing/base.rb +1 -0
  44. data/lib/dragonfly/processing/r_magick_processor.rb +12 -127
  45. data/lib/dragonfly/processing/r_magick_text_processor.rb +155 -0
  46. data/lib/dragonfly/processor_list.rb +4 -0
  47. data/lib/dragonfly/rails/images.rb +2 -14
  48. data/lib/dragonfly/temp_object.rb +41 -34
  49. data/spec/dragonfly/active_record_extensions/migration.rb +7 -0
  50. data/spec/dragonfly/active_record_extensions/model_spec.rb +10 -11
  51. data/spec/dragonfly/active_record_extensions/spec_helper.rb +1 -1
  52. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +14 -1
  53. data/spec/dragonfly/belongs_to_app_spec.rb +55 -0
  54. data/spec/dragonfly/configurable_spec.rb +21 -6
  55. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +1 -0
  56. data/spec/dragonfly/delegator_spec.rb +23 -12
  57. data/spec/dragonfly/extended_temp_object_spec.rb +13 -29
  58. data/spec/dragonfly/processing/{rmagick_processor_spec.rb → r_magick_processor_spec.rb} +8 -75
  59. data/spec/dragonfly/processing/r_magick_text_processor_spec.rb +84 -0
  60. data/spec/dragonfly/temp_object_spec.rb +126 -151
  61. data/spec/dragonfly_spec.rb +12 -0
  62. data/spec/ginger_scenarios.rb +2 -2
  63. data/spec/image_matchers.rb +2 -2
  64. data/yard/setup.rb +12 -2
  65. data/yard/templates/default/fulldoc/html/css/common.css +1 -2
  66. data/yard/templates/default/layout/html/layout.erb +3 -2
  67. metadata +21 -18
  68. data/History.txt +0 -75
  69. data/features/rails_3.0.0.beta.feature +0 -15
  70. data/fixtures/dragonfly_setup.rb +0 -1
  71. data/fixtures/rails +0 -22
  72. data/fixtures/rails_3.0.0.beta/template.rb +0 -13
  73. data/generators/dragonfly_app/USAGE +0 -16
  74. data/generators/dragonfly_app/dragonfly_app_generator.rb +0 -24
  75. data/generators/dragonfly_app/templates/initializer.erb +0 -35
  76. data/lib/dragonfly/r_magick_configuration.rb +0 -67
  77. data/spec/dragonfly/active_record_extensions/initializer.rb +0 -1
@@ -0,0 +1,155 @@
1
+ require 'RMagick'
2
+
3
+ module Dragonfly
4
+ module Processing
5
+
6
+ class RMagickTextProcessor < Base
7
+
8
+ FONT_STYLES = {
9
+ 'normal' => Magick::NormalStyle,
10
+ 'italic' => Magick::ItalicStyle,
11
+ 'oblique' => Magick::ObliqueStyle
12
+ }
13
+
14
+ FONT_STRETCHES = {
15
+ 'normal' => Magick::NormalStretch,
16
+ 'semi-condensed' => Magick::SemiCondensedStretch,
17
+ 'condensed' => Magick::CondensedStretch,
18
+ 'extra-condensed' => Magick::ExtraCondensedStretch,
19
+ 'ultra-condensed' => Magick::UltraCondensedStretch,
20
+ 'semi-expanded' => Magick::SemiExpandedStretch,
21
+ 'expanded' => Magick::ExpandedStretch,
22
+ 'extra-expanded' => Magick::ExtraExpandedStretch,
23
+ 'ultra-expanded' => Magick::UltraExpandedStretch
24
+ }
25
+
26
+ FONT_WEIGHTS = {
27
+ 'normal' => Magick::NormalWeight,
28
+ 'bold' => Magick::BoldWeight,
29
+ 'bolder' => Magick::BolderWeight,
30
+ 'lighter' => Magick::LighterWeight,
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
+ # HashWithCssStyleKeys is solely for being able to access a hash
43
+ # which has css-style keys (e.g. 'font-size') with the underscore
44
+ # symbol version
45
+ # @example
46
+ # opts = {'font-size' => '23px', :color => 'white'}
47
+ # opts = HashWithCssStyleKeys[opts]
48
+ # opts[:font_size] # ===> '23px'
49
+ # opts[:color] # ===> 'white'
50
+ class HashWithCssStyleKeys < Hash
51
+ def [](key)
52
+ super || (
53
+ str_key = key.to_s
54
+ css_key = str_key.gsub('_','-')
55
+ super(str_key) || super(css_key) || super(css_key.to_sym)
56
+ )
57
+ end
58
+ end
59
+
60
+ def text(temp_object, opts={})
61
+ opts = HashWithCssStyleKeys[opts]
62
+
63
+ draw = Magick::Draw.new
64
+ draw.gravity = Magick::CenterGravity
65
+ draw.text_antialias = true
66
+
67
+ # Font size
68
+ font_size = (opts[:font_size] || 12).to_i
69
+
70
+ # Scale up the text for better quality -
71
+ # it will be reshrunk at the end
72
+ s = scale_factor_for(font_size)
73
+
74
+ # Settings
75
+ draw.pointsize = font_size * s
76
+ draw.font = opts[:font] if opts[:font]
77
+ draw.font_family = opts[:font_family] if opts[:font_family]
78
+ draw.fill = opts[:color] if opts[:color]
79
+ draw.stroke = opts[:stroke_color] if opts[:stroke_color]
80
+ draw.font_style = FONT_STYLES[opts[:font_style]] if opts[:font_style]
81
+ draw.font_stretch = FONT_STRETCHES[opts[:font_stretch]] if opts[:font_stretch]
82
+ draw.font_weight = FONT_WEIGHTS[opts[:font_weight]] if opts[:font_weight]
83
+
84
+ # Padding
85
+ # NB the values are scaled up by the scale factor
86
+ pt, pr, pb, pl = parse_padding_string(opts[:padding]) if opts[:padding]
87
+ padding_top = (opts[:padding_top] || pt || 0) * s
88
+ padding_right = (opts[:padding_right] || pr || 0) * s
89
+ padding_bottom = (opts[:padding_bottom] || pb || 0) * s
90
+ padding_left = (opts[:padding_left] || pl || 0) * s
91
+
92
+ text = temp_object.data
93
+
94
+ # Calculate (scaled up) dimensions
95
+ metrics = draw.get_type_metrics(text)
96
+ width, height = metrics.width, metrics.height
97
+
98
+ scaled_up_width = padding_left + width + padding_right
99
+ scaled_up_height = padding_top + height + padding_bottom
100
+
101
+ # Draw the background
102
+ image = Magick::Image.new(scaled_up_width, scaled_up_height){
103
+ self.background_color = opts[:background_color] || 'transparent'
104
+ }
105
+ # Draw the text
106
+ draw.annotate(image, width, height, padding_left, padding_top, text)
107
+
108
+ # Scale back down again
109
+ image.scale!(1/s)
110
+
111
+ image.format = 'png'
112
+
113
+ # Output image as string
114
+ image.to_blob
115
+ end
116
+
117
+ private
118
+
119
+ # Use css-style padding declaration, i.e.
120
+ # 10 (all sides)
121
+ # 10 5 (top/bottom, left/right)
122
+ # 10 5 10 (top, left/right, bottom)
123
+ # 10 5 10 5 (top, right, bottom, left)
124
+ def parse_padding_string(str)
125
+ padding_parts = str.gsub('px','').split(/\s+/).map{|px| px.to_i}
126
+ case padding_parts.size
127
+ when 1
128
+ p = padding_parts.first
129
+ [p,p,p,p]
130
+ when 2
131
+ p,q = padding_parts
132
+ [p,q,p,q]
133
+ when 3
134
+ p,q,r = padding_parts
135
+ [p,q,r,q]
136
+ when 4
137
+ padding_parts
138
+ else raise ArgumentError, "Couldn't parse padding string '#{str}' - should be a css-style string"
139
+ end
140
+ end
141
+
142
+ def scale_factor_for(font_size)
143
+ # Scale approximately to 64 if below
144
+ min_size = 64
145
+ if font_size < min_size
146
+ (min_size.to_f / font_size).ceil
147
+ else
148
+ 1
149
+ end.to_f
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+ end
@@ -1,5 +1,9 @@
1
1
  module Dragonfly
2
2
  class ProcessorList
3
3
  include Delegator
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ end
4
8
  end
5
9
  end
@@ -2,23 +2,11 @@ require 'dragonfly'
2
2
  require 'rack/cache'
3
3
 
4
4
  ### The dragonfly app ###
5
-
6
5
  app = Dragonfly::App[:images]
7
- app.configure_with(Dragonfly::RMagickConfiguration)
8
- app.configure do |c|
9
- c.log = Rails.logger
10
- c.datastore.configure do |d|
11
- d.root_path = "#{Rails.root}/public/system/dragonfly/#{Rails.env}"
12
- end
13
- c.url_handler.configure do |u|
14
- u.protect_from_dos_attacks = false
15
- u.path_prefix = '/media'
16
- end
17
- end
6
+ app.configure_with(Dragonfly::Config::RailsImages)
18
7
 
19
8
  ### Extend active record ###
20
- ActiveRecord::Base.extend Dragonfly::ActiveRecordExtensions
21
- ActiveRecord::Base.register_dragonfly_app(:image, app)
9
+ Dragonfly.active_record_macro(:image, app)
22
10
 
23
11
  ### Insert the middleware ###
24
12
  # Where the middleware is depends on the version of Rails
@@ -53,26 +53,34 @@ module Dragonfly
53
53
  end
54
54
 
55
55
  def data
56
- @data ||= initialized_data || file.open.read
56
+ @data ||= initialized_data || file.read
57
57
  end
58
58
 
59
59
  def tempfile
60
- if @tempfile
60
+ @tempfile ||= begin
61
+ if initialized_tempfile
62
+ @tempfile = initialized_tempfile
63
+ elsif initialized_data
64
+ @tempfile = Tempfile.new('dragonfly')
65
+ @tempfile.write(initialized_data)
66
+ elsif initialized_file
67
+ @tempfile = copy_to_tempfile(initialized_file.path)
68
+ end
69
+ @tempfile.close
61
70
  @tempfile
62
- elsif initialized_tempfile
63
- initialized_tempfile.open
64
- @tempfile = initialized_tempfile
65
- elsif initialized_data
66
- tempfile = Tempfile.new('dragonfly')
67
- tempfile.write(initialized_data)
68
- tempfile.open
69
- @tempfile = tempfile
70
- elsif initialized_file
71
- @tempfile = copy_to_tempfile(initialized_file)
72
71
  end
73
72
  end
74
-
75
- alias_method :file, :tempfile
73
+
74
+ def file(&block)
75
+ f = tempfile.open
76
+ if block_given?
77
+ ret = yield f
78
+ f.close
79
+ else
80
+ ret = f
81
+ end
82
+ ret
83
+ end
76
84
 
77
85
  def path
78
86
  tempfile.path
@@ -104,17 +112,10 @@ module Dragonfly
104
112
  end
105
113
 
106
114
  def each(&block)
107
- if initialized_data
108
- string_io = StringIO.new(initialized_data)
109
- while part = string_io.read(block_size)
110
- yield part
111
- end
112
- else
113
- tempfile.open
114
- while part = tempfile.read(block_size)
115
+ to_io do |io|
116
+ while part = io.read(block_size)
115
117
  yield part
116
118
  end
117
- tempfile.close
118
119
  end
119
120
  end
120
121
 
@@ -127,6 +128,14 @@ module Dragonfly
127
128
  File.new(path)
128
129
  end
129
130
 
131
+ def to_io(&block)
132
+ if initialized_data
133
+ StringIO.open(initialized_data, &block)
134
+ else
135
+ file(&block)
136
+ end
137
+ end
138
+
130
139
  protected
131
140
 
132
141
  attr_accessor :initialized_data, :initialized_tempfile, :initialized_file
@@ -134,16 +143,14 @@ module Dragonfly
134
143
  private
135
144
 
136
145
  def reset!
137
- instance_variables.each do |var|
138
- instance_variable_set(var, nil)
139
- end
146
+ @data = @tempfile = @initialized_data = @initialized_file = @initialized_tempfile = nil
140
147
  end
141
148
 
142
149
  def initialize_from_object!(obj)
143
150
  case obj
144
151
  when TempObject
145
152
  @initialized_data = obj.initialized_data
146
- @initialized_tempfile = copy_to_tempfile(obj.initialized_tempfile) if obj.initialized_tempfile
153
+ @initialized_tempfile = copy_to_tempfile(obj.initialized_tempfile.path) if obj.initialized_tempfile
147
154
  @initialized_file = obj.initialized_file
148
155
  when String
149
156
  @initialized_data = obj
@@ -153,20 +160,20 @@ module Dragonfly
153
160
  @initialized_file = obj
154
161
  self.name = File.basename(obj.path)
155
162
  else
156
- raise ArgumentError, "#{self.class.name} must be initialized with a String, a File or a Tempfile"
163
+ raise ArgumentError, "#{self.class.name} must be initialized with a String, a File, a Tempfile, or another TempObject"
157
164
  end
158
165
  self.name = obj.original_filename if obj.respond_to?(:original_filename)
159
166
  end
160
167
 
161
- def copy_to_tempfile(file)
162
- tempfile = Tempfile.new('dragonfly')
163
- FileUtils.cp File.expand_path(file.path), tempfile.path
164
- tempfile
165
- end
166
-
167
168
  def block_size
168
169
  self.class.block_size
169
170
  end
170
171
 
172
+ def copy_to_tempfile(path)
173
+ tempfile = Tempfile.new('dragonfly')
174
+ FileUtils.cp File.expand_path(path), tempfile.path
175
+ tempfile
176
+ end
177
+
171
178
  end
172
179
  end
@@ -1,3 +1,10 @@
1
+ # Seems to blow up on Rails 3 without this
2
+ begin
3
+ require 'active_support/all'
4
+ rescue LoadError => e
5
+ puts "Couldn't load activesupport: #{e}"
6
+ end
7
+
1
8
  class MigrationForTest < ActiveRecord::Migration
2
9
 
3
10
  def self.up
@@ -6,18 +6,17 @@ describe Item do
6
6
 
7
7
  describe "registering dragonfly apps" do
8
8
 
9
- before(:each) do
10
- @app1, @app2 = Dragonfly::App[:images], Dragonfly::App[:videos]
11
- ActiveRecord::Base.register_dragonfly_app(:image, @app1)
12
- ActiveRecord::Base.register_dragonfly_app(:video, @app2)
13
- end
14
-
9
+ let(:app1){ Dragonfly::App[:images] }
10
+ let(:app2){ Dragonfly::App[:videos] }
11
+
15
12
  it "should return the mapping of apps to attributes" do
13
+ Dragonfly.active_record_macro(:image, app1)
14
+ Dragonfly.active_record_macro(:video, app2)
16
15
  Item.class_eval do
17
16
  image_accessor :preview_image
18
17
  video_accessor :trailer_video
19
18
  end
20
- Item.dragonfly_apps_for_attributes.should == {:preview_image => @app1, :trailer_video => @app2}
19
+ Item.dragonfly_apps_for_attributes.should == {:preview_image => app1, :trailer_video => app2}
21
20
  end
22
21
 
23
22
  end
@@ -36,7 +35,7 @@ describe Item do
36
35
 
37
36
  before(:each) do
38
37
  @app = Dragonfly::App[:images]
39
- ActiveRecord::Base.register_dragonfly_app(:image, @app)
38
+ Dragonfly.active_record_macro(:image, @app)
40
39
  Item.class_eval do
41
40
  image_accessor :preview_image
42
41
  end
@@ -259,7 +258,7 @@ describe Item do
259
258
 
260
259
  before(:all) do
261
260
  @app = Dragonfly::App[:images]
262
- ActiveRecord::Base.register_dragonfly_app(:image, @app)
261
+ Dragonfly.active_record_macro(:image, @app)
263
262
  end
264
263
 
265
264
  describe "validates_presence_of" do
@@ -417,7 +416,7 @@ describe Item do
417
416
  def number_of_As(temp_object); temp_object.data.count('A'); end
418
417
  end
419
418
  @app.register_analyser(custom_analyser)
420
- ActiveRecord::Base.register_dragonfly_app(:image, @app)
419
+ Dragonfly.active_record_macro(:image, @app)
421
420
  Item.class_eval do
422
421
  image_accessor :preview_image
423
422
  end
@@ -549,7 +548,7 @@ describe Item do
549
548
  describe "inheritance" do
550
549
  before(:all) do
551
550
  @app = Dragonfly::App[:images]
552
- ActiveRecord::Base.register_dragonfly_app(:image, @app)
551
+ Dragonfly.active_record_macro(:image, @app)
553
552
  Car.class_eval do
554
553
  image_accessor :image
555
554
  end
@@ -5,7 +5,7 @@ require 'active_record'
5
5
 
6
6
  DB_FILE = File.expand_path(File.dirname(__FILE__)+'/db.sqlite3')
7
7
 
8
- %w{migration initializer models}.each do |file|
8
+ %w{migration models}.each do |file|
9
9
  require "#{File.dirname(__FILE__)}/#{file}"
10
10
  end
11
11
 
@@ -28,4 +28,17 @@ describe Dragonfly::Analysis::RMagickAnalyser do
28
28
  @analyser.depth(@beach).should == 8
29
29
  end
30
30
 
31
- end
31
+ it "should return the format" do
32
+ @analyser.format(@beach).should == :png
33
+ end
34
+
35
+ %w(width height aspect_ratio number_of_colours depth format).each do |meth|
36
+ it "should throw unable_to_handle in #{meth.inspect} if it's not an image file" do
37
+ temp_object = Dragonfly::TempObject.new('blah')
38
+ lambda{
39
+ @analyser.send(meth, temp_object)
40
+ }.should throw_symbol(:unable_to_handle)
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ class Testoast
4
+ include Dragonfly::BelongsToApp
5
+ end
6
+
7
+ describe Dragonfly::BelongsToApp do
8
+
9
+ before(:each) do
10
+ @object = Testoast.new
11
+ end
12
+
13
+ describe "when the app is not set" do
14
+ it "should raise an error if the app is accessed" do
15
+ lambda{
16
+ @object.app
17
+ }.should raise_error(Dragonfly::BelongsToApp::NotConfigured)
18
+ end
19
+ it "should say it's not set" do
20
+ @object.app_set?.should be_false
21
+ end
22
+ it "should still return a log" do
23
+ @object.log.should be_a(Logger)
24
+ end
25
+ it "should cache the log" do
26
+ @object.log.should == @object.log
27
+ end
28
+ it "should return the app's log if it's subsequently set" do
29
+ @object.log.should be_a(Logger)
30
+ @object.app = (app = mock('app', :log => mock))
31
+ @object.log.should == app.log
32
+ end
33
+ end
34
+
35
+ describe "when the app is set" do
36
+ before(:each) do
37
+ @app = mock('app', :log => mock)
38
+ @object.app = @app
39
+ end
40
+
41
+ it "should return the app" do
42
+ @object.app.should == @app
43
+ end
44
+
45
+ it "should return the app's log" do
46
+ @object.log.should == @app.log
47
+ end
48
+
49
+ it "should say it's set" do
50
+ @object.app_set?.should be_true
51
+ end
52
+
53
+ end
54
+
55
+ end