jr-paperclip 7.3.0 → 8.0.0.beta.1

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{test.yml → tests.yml} +19 -9
  3. data/.rubocop.yml +2 -1
  4. data/CONTRIBUTING.md +1 -1
  5. data/Gemfile +1 -0
  6. data/NEWS +16 -1
  7. data/README.md +119 -8
  8. data/UPGRADING +5 -0
  9. data/VIPS_MIGRATION_GUIDE.md +131 -0
  10. data/features/basic_integration.feature +27 -0
  11. data/features/step_definitions/attachment_steps.rb +17 -0
  12. data/gemfiles/7.0.gemfile +1 -0
  13. data/gemfiles/7.1.gemfile +1 -0
  14. data/gemfiles/7.2.gemfile +1 -0
  15. data/gemfiles/8.0.gemfile +1 -0
  16. data/gemfiles/8.1.gemfile +1 -0
  17. data/lib/paperclip/attachment.rb +3 -2
  18. data/lib/paperclip/errors.rb +4 -5
  19. data/lib/paperclip/geometry.rb +3 -3
  20. data/lib/paperclip/geometry_detector_factory.rb +52 -12
  21. data/lib/paperclip/helpers.rb +18 -0
  22. data/lib/paperclip/processor.rb +36 -4
  23. data/lib/paperclip/thumbnail.rb +568 -62
  24. data/lib/paperclip/version.rb +1 -1
  25. data/lib/paperclip.rb +26 -9
  26. data/paperclip.gemspec +3 -2
  27. data/spec/paperclip/attachment_definitions_spec.rb +300 -0
  28. data/spec/paperclip/attachment_spec.rb +1 -1
  29. data/spec/paperclip/geometry_detector_spec.rb +81 -32
  30. data/spec/paperclip/geometry_spec.rb +8 -5
  31. data/spec/paperclip/helpers_spec.rb +49 -0
  32. data/spec/paperclip/lazy_thumbnail_compatibility_spec.rb +266 -0
  33. data/spec/paperclip/processor_spec.rb +35 -1
  34. data/spec/paperclip/style_spec.rb +58 -0
  35. data/spec/paperclip/thumbnail_custom_options_spec.rb +173 -0
  36. data/spec/paperclip/thumbnail_loader_options_spec.rb +53 -0
  37. data/spec/paperclip/thumbnail_security_spec.rb +42 -0
  38. data/spec/paperclip/thumbnail_spec.rb +1127 -172
  39. metadata +36 -4
@@ -1,31 +1,75 @@
1
1
  module Paperclip
2
2
  class GeometryDetector
3
- def initialize(file)
3
+ def initialize(file, backend: nil)
4
4
  @file = file
5
+ @backend = backend
5
6
  raise_if_blank_file
6
7
  end
7
8
 
8
9
  def make
9
10
  geometry = GeometryParser.new(geometry_string.strip).make
10
- geometry || raise(Errors::NotIdentifiedByImageMagickError.new("Could not identify image size"))
11
+ geometry || raise(Errors::NotIdentifiedByBackendError.new("Could not identify image size"))
11
12
  end
12
13
 
13
14
  private
14
15
 
15
16
  def geometry_string
16
- orientation = Paperclip.options[:use_exif_orientation] ?
17
- "%[exif:orientation]" : "1"
17
+ if resolve_backend == :vips
18
+ vips_geometry_string
19
+ else
20
+ imagemagick_geometry_string
21
+ end
22
+ end
23
+
24
+ def resolve_backend
25
+ Paperclip.resolve_backend(@backend || Paperclip.options[:backend])
26
+ end
27
+
28
+ def imagemagick_geometry_string
29
+ orientation = if Paperclip.options[:use_exif_orientation]
30
+ "%[exif:orientation]"
31
+ else
32
+ "1"
33
+ end
18
34
  Paperclip.run(
19
- Paperclip.options[:is_windows] ? "magick identify" : "identify",
35
+ (Paperclip.options[:is_windows] || Paperclip.imagemagick7?) ? "magick identify" : "identify",
20
36
  "-format '%wx%h,#{orientation}' :file", {
21
- file: "#{path}[0]"
37
+ file: "#{path}[0]",
22
38
  },
23
39
  swallow_stderr: true
24
40
  )
25
41
  rescue Terrapin::ExitStatusError
26
42
  ""
27
43
  rescue Terrapin::CommandNotFoundError => e
28
- raise_because_imagemagick_missing
44
+ raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
45
+ end
46
+
47
+ def vips_geometry_string
48
+ begin
49
+ require "vips"
50
+ rescue LoadError => e
51
+ raise Errors::CommandNotFoundError.new("Could not load ruby-vips. Please install libvips and the image_processing gem.")
52
+ end
53
+
54
+ begin
55
+ # Use ruby-vips gem directly instead of shelling out to vipsheader
56
+ image = Vips::Image.new_from_file(path, access: :sequential)
57
+ width = image.width
58
+ height = image.height
59
+
60
+ orientation = "1"
61
+ if Paperclip.options[:use_exif_orientation]
62
+ begin
63
+ orientation = image.get("orientation").to_s
64
+ rescue Vips::Error
65
+ # Field might not exist
66
+ end
67
+ end
68
+
69
+ "#{width}x#{height},#{orientation}"
70
+ rescue Vips::Error
71
+ ""
72
+ end
29
73
  end
30
74
 
31
75
  def path
@@ -34,12 +78,8 @@ module Paperclip
34
78
 
35
79
  def raise_if_blank_file
36
80
  if path.blank?
37
- raise Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")
81
+ raise Errors::NotIdentifiedByBackendError.new("Cannot find the geometry of a file with a blank name")
38
82
  end
39
83
  end
40
-
41
- def raise_because_imagemagick_missing
42
- raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
43
- end
44
84
  end
45
85
  end
@@ -56,5 +56,23 @@ module Paperclip
56
56
  def reset_duplicate_clash_check!
57
57
  @names_url = nil
58
58
  end
59
+
60
+ def imagemagick7?
61
+ return @imagemagick7 if instance_variable_defined?(:@imagemagick7)
62
+
63
+ @imagemagick7 = !!which("magick")
64
+ end
65
+
66
+ # https://github.com/minimagick/minimagick/blob/master/lib/mini_magick/utilities.rb#L9-L24
67
+ def which(cmd)
68
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
69
+ (ENV["PATH"] || "").split(File::PATH_SEPARATOR).each do |path|
70
+ exts.each do |ext|
71
+ exe = File.join(path, "#{cmd}#{ext}")
72
+ return exe if File.executable? exe
73
+ end
74
+ end
75
+ nil
76
+ end
59
77
  end
60
78
  end
@@ -36,10 +36,18 @@ module Paperclip
36
36
  # The convert method runs the convert binary with the provided arguments.
37
37
  # See Paperclip.run for the available options.
38
38
  def convert(arguments = "", local_options = {})
39
+ command =
40
+ if Paperclip.imagemagick7? # IMv7 on any OS
41
+ "magick"
42
+ elsif Paperclip.options[:is_windows] # IMv6 on Windows
43
+ "magick convert"
44
+ else
45
+ "convert"
46
+ end
39
47
  Paperclip.run(
40
- Paperclip.options[:is_windows] ? "magick convert" : "convert",
48
+ command,
41
49
  arguments,
42
- local_options
50
+ local_options,
43
51
  )
44
52
  end
45
53
 
@@ -47,10 +55,34 @@ module Paperclip
47
55
  # See Paperclip.run for the available options.
48
56
  def identify(arguments = "", local_options = {})
49
57
  Paperclip.run(
50
- Paperclip.options[:is_windows] ? "magick identify" : "identify",
58
+ (Paperclip.options[:is_windows] || Paperclip.imagemagick7?) ? "magick identify" : "identify",
51
59
  arguments,
52
- local_options
60
+ local_options,
53
61
  )
54
62
  end
63
+
64
+ # Runs libvips command
65
+ def vips(arguments = "", local_options = {})
66
+ Paperclip.run("vips", arguments, local_options)
67
+ end
68
+
69
+ # Runs libvips header command
70
+ def vipsheader(arguments = "", local_options = {})
71
+ Paperclip.run("vipsheader", arguments, local_options)
72
+ end
73
+
74
+ # Returns a Vips::Image object for the given file path.
75
+ # This provides access to the full ruby-vips API for image manipulation.
76
+ # @param file_path [String] Path to the image file
77
+ # @param options [Hash] Options to pass to Vips::Image.new_from_file
78
+ # @return [Vips::Image] The loaded image
79
+ def vips_image(file_path, **options)
80
+ begin
81
+ require "vips"
82
+ rescue LoadError
83
+ raise Errors::CommandNotFoundError.new("Could not load ruby-vips. Please install libvips and the vips gem.")
84
+ end
85
+ Vips::Image.new_from_file(file_path, **options)
86
+ end
55
87
  end
56
88
  end