jekyll_picture_tag 1.8.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.travis.yml +4 -7
  4. data/Dockerfile +9 -0
  5. data/docs/Gemfile.lock +183 -88
  6. data/docs/contributing.md +50 -16
  7. data/docs/example_presets.md +1 -1
  8. data/docs/global_configuration.md +55 -2
  9. data/docs/index.md +27 -21
  10. data/docs/installation.md +22 -7
  11. data/docs/presets.md +137 -55
  12. data/docs/releases.md +20 -1
  13. data/docs/usage.md +83 -39
  14. data/jekyll_picture_tag.gemspec +1 -1
  15. data/lib/jekyll_picture_tag.rb +28 -10
  16. data/lib/jekyll_picture_tag/cache.rb +3 -0
  17. data/lib/jekyll_picture_tag/cache/base.rb +59 -0
  18. data/lib/jekyll_picture_tag/cache/generated.rb +20 -0
  19. data/lib/jekyll_picture_tag/cache/source.rb +19 -0
  20. data/lib/jekyll_picture_tag/defaults/global.yml +2 -0
  21. data/lib/jekyll_picture_tag/defaults/presets.yml +2 -0
  22. data/lib/jekyll_picture_tag/generated_image.rb +85 -20
  23. data/lib/jekyll_picture_tag/img_uri.rb +1 -0
  24. data/lib/jekyll_picture_tag/instructions.rb +1 -0
  25. data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +69 -0
  26. data/lib/jekyll_picture_tag/instructions/configuration.rb +47 -11
  27. data/lib/jekyll_picture_tag/instructions/preset.rb +35 -14
  28. data/lib/jekyll_picture_tag/instructions/set.rb +18 -8
  29. data/lib/jekyll_picture_tag/instructions/tag_parser.rb +59 -69
  30. data/lib/jekyll_picture_tag/output_formats/basic.rb +42 -11
  31. data/lib/jekyll_picture_tag/output_formats/img.rb +11 -0
  32. data/lib/jekyll_picture_tag/output_formats/picture.rb +22 -0
  33. data/lib/jekyll_picture_tag/router.rb +17 -0
  34. data/lib/jekyll_picture_tag/source_image.rb +60 -39
  35. data/lib/jekyll_picture_tag/srcsets/basic.rb +54 -19
  36. data/lib/jekyll_picture_tag/srcsets/pixel_ratio.rb +1 -3
  37. data/lib/jekyll_picture_tag/srcsets/width.rb +1 -1
  38. data/lib/jekyll_picture_tag/utils.rb +18 -0
  39. data/lib/jekyll_picture_tag/version.rb +1 -1
  40. data/readme.md +40 -16
  41. metadata +14 -8
@@ -6,6 +6,10 @@ module PictureTag
6
6
  class Basic
7
7
  include ObjectiveElements
8
8
 
9
+ def to_s
10
+ wrap(base_markup).to_s
11
+ end
12
+
9
13
  # Used for both the fallback image, and for the complete markup.
10
14
  def build_base_img
11
15
  img = SingleTag.new 'img'
@@ -23,10 +27,6 @@ module PictureTag
23
27
  img
24
28
  end
25
29
 
26
- def to_s
27
- wrap(base_markup).to_s
28
- end
29
-
30
30
  private
31
31
 
32
32
  # Handles various wrappers around basic markup
@@ -69,12 +69,32 @@ module PictureTag
69
69
  element.media = srcset.media_attribute if srcset.media
70
70
  end
71
71
 
72
- # File, not HTML
72
+ # GeneratedImage class, not HTML
73
73
  def build_fallback_image
74
- GeneratedImage.new(
74
+ return fallback_candidate if fallback_candidate.exists?
75
+
76
+ image = GeneratedImage.new(
77
+ source_file: PictureTag.source_images.first,
78
+ format: PictureTag.fallback_format,
79
+ width: checked_fallback_width,
80
+ crop: PictureTag.crop,
81
+ gravity: PictureTag.gravity
82
+ )
83
+
84
+ image.generate
85
+
86
+ image
87
+ end
88
+
89
+ # It's only a candidate, because we don't know if the fallback width
90
+ # setting is larger than the source file.
91
+ def fallback_candidate
92
+ @fallback_candidate ||= GeneratedImage.new(
75
93
  source_file: PictureTag.source_images.first,
76
94
  format: PictureTag.fallback_format,
77
- width: checked_fallback_width
95
+ width: PictureTag.fallback_width,
96
+ crop: PictureTag.crop,
97
+ gravity: PictureTag.gravity
78
98
  )
79
99
  end
80
100
 
@@ -95,15 +115,26 @@ module PictureTag
95
115
  content.add_parent anchor
96
116
  end
97
117
 
118
+ def source
119
+ PictureTag.source_images.first
120
+ end
121
+
122
+ def source_width
123
+ if PictureTag.crop
124
+ fallback_candidate.source_width
125
+ else
126
+ source.width
127
+ end
128
+ end
129
+
98
130
  def checked_fallback_width
99
- source = PictureTag.source_images.first
100
131
  target = PictureTag.fallback_width
101
132
 
102
- if target > source.width
133
+ if target > source_width
103
134
  Utils.warning "#{source.shortname} is smaller than the " \
104
- "requested fallback width of #{target}px. Using #{source.width}" \
135
+ "requested fallback width of #{target}px. Using #{source_width}" \
105
136
  ' px instead.'
106
- source.width
137
+ source_width
107
138
  else
108
139
  target
109
140
  end
@@ -3,6 +3,8 @@ module PictureTag
3
3
  # Represents a bare <img> tag with a srcset attribute.
4
4
  # Used when <picture> is unnecessary.
5
5
  class Img < Basic
6
+ private
7
+
6
8
  def srcset
7
9
  @srcset ||= build_srcset(
8
10
  PictureTag.source_images.first, PictureTag.formats.first
@@ -17,8 +19,17 @@ module PictureTag
17
19
 
18
20
  img.attributes << PictureTag.html_attributes['parent']
19
21
 
22
+ add_dimensions(img, srcset)
23
+
20
24
  img
21
25
  end
26
+
27
+ def add_dimensions(img, srcset)
28
+ return unless PictureTag.preset['dimension_attributes']
29
+
30
+ img.width = srcset.width_attribute
31
+ img.height = srcset.height_attribute
32
+ end
22
33
  end
23
34
  end
24
35
  end
@@ -3,7 +3,13 @@ module PictureTag
3
3
  # Represents a <picture> tag, enclosing at least 2 <source> tags and an
4
4
  # <img> tag.
5
5
  class Picture < Basic
6
+ private
7
+
6
8
  def srcsets
9
+ @srcsets ||= build_srcsets
10
+ end
11
+
12
+ def build_srcsets
7
13
  formats = PictureTag.formats
8
14
  # Source images are provided in reverse order and must be flipped:
9
15
  images = PictureTag.source_images.reverse
@@ -37,6 +43,22 @@ module PictureTag
37
43
  source
38
44
  end
39
45
 
46
+ def build_base_img
47
+ img = super
48
+
49
+ # Only add source dimensions if there is a single source image.
50
+ # Currently you can't use both art direction and intrinsic image sizes.
51
+ if PictureTag.preset['dimension_attributes'] &&
52
+ PictureTag.source_images.length == 1
53
+ img.attributes << {
54
+ width: srcsets.first.width_attribute,
55
+ height: srcsets.first.height_attribute
56
+ }
57
+ end
58
+
59
+ img
60
+ end
61
+
40
62
  def base_markup
41
63
  picture = DoubleTag.new(
42
64
  'picture',
@@ -9,6 +9,7 @@ module PictureTag
9
9
  # gets big, I'll refactor.
10
10
  module Router
11
11
  attr_accessor :instructions, :context
12
+
12
13
  # Context forwarding
13
14
 
14
15
  # Global site data
@@ -47,6 +48,14 @@ module PictureTag
47
48
  @instructions.source_images
48
49
  end
49
50
 
51
+ def crop(media = nil)
52
+ @instructions.crop(media)
53
+ end
54
+
55
+ def gravity(media = nil)
56
+ @instructions.gravity(media)
57
+ end
58
+
50
59
  # Config Forwarding
51
60
 
52
61
  def source_dir
@@ -69,6 +78,14 @@ module PictureTag
69
78
  config.pconfig
70
79
  end
71
80
 
81
+ def disabled?
82
+ config.disabled?
83
+ end
84
+
85
+ def fast_build?
86
+ config.fast_build?
87
+ end
88
+
72
89
  # Preset forwarding
73
90
 
74
91
  def widths(media)
@@ -3,79 +3,100 @@ module PictureTag
3
3
  # advantage by storing expensive file reads and writes in instance variables,
4
4
  # to be reused by many different generated images.
5
5
  class SourceImage
6
- attr_reader :name, :shortname, :missing, :media_preset
6
+ attr_reader :shortname, :missing, :media_preset
7
+
7
8
  include MiniMagick
8
9
 
9
10
  def initialize(relative_filename, media_preset = nil)
11
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
12
+ # ^^^^^^^^^^^^^^^^^^^^^^
10
13
  @shortname = relative_filename
11
- @name = grab_file relative_filename
12
14
  @media_preset = media_preset
15
+
16
+ @missing = missing?
17
+ check_cache
18
+ end
19
+
20
+ def digest
21
+ @digest ||= cache[:digest] || ''
13
22
  end
14
23
 
15
24
  def width
16
- @width ||= if @missing
17
- 999_999
18
- else
19
- image.width
20
- end
25
+ @width ||= cache[:width] || 999_999
21
26
  end
22
27
 
23
- def digest
24
- @digest ||= if @missing
25
- 'x' * 6
26
- else
27
- Digest::MD5.hexdigest(File.read(@name))[0..5]
28
- end
28
+ def height
29
+ @height ||= cache[:height] || 999_999
29
30
  end
30
31
 
31
- # Includes path relative to default sorce folder, and the original filename.
32
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
33
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34
+ def name
35
+ @name ||= File.join(PictureTag.source_dir, @shortname)
36
+ end
37
+
38
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
39
+ # ^^^^^^^^^^^^^^^^^^
32
40
  def base_name
33
41
  @shortname.delete_suffix File.extname(@shortname)
34
42
  end
35
43
 
36
- # File exention
44
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
45
+ # ^^^
37
46
  def ext
38
- # [1..-1] will strip the leading period.
39
- @ext ||= File.extname(@name)[1..-1].downcase
47
+ @ext ||= File.extname(name)[1..-1].downcase
40
48
  end
41
49
 
42
50
  private
43
51
 
44
- def image
45
- @image ||= Image.open(@name)
52
+ def cache
53
+ @cache ||= Cache::Source.new(@shortname)
46
54
  end
47
55
 
48
- # Turn a relative filename into an absolute one, and make sure it exists.
49
- def grab_file(source_file)
50
- source_name = File.join(PictureTag.source_dir, source_file)
51
-
52
- if File.exist? source_name
53
- @missing = false
56
+ def missing?
57
+ if File.exist? name
58
+ false
54
59
 
55
60
  elsif PictureTag.continue_on_missing?
56
- @missing = true
57
- Utils.warning missing_image_warning(source_name)
61
+ Utils.warning(missing_image_warning)
62
+ true
58
63
 
59
64
  else
60
- raise ArgumentError, missing_image_error(source_name)
65
+ raise ArgumentError, missing_image_error
61
66
  end
67
+ end
62
68
 
63
- source_name
69
+ def check_cache
70
+ return if @missing
71
+ return if cache[:digest] && PictureTag.fast_build?
72
+
73
+ update_cache if source_digest != cache[:digest]
64
74
  end
65
75
 
66
- def missing_image_warning(source_name)
67
- <<~HEREDOC
68
- Could not find #{source_name}. Your site will have broken images in it.
69
- Continuing.
70
- HEREDOC
76
+ def update_cache
77
+ cache[:digest] = source_digest
78
+ cache[:width] = image.width
79
+ cache[:height] = image.height
80
+
81
+ cache.write
82
+ end
83
+
84
+ def image
85
+ @image ||= Image.open(name)
86
+ end
87
+
88
+ def source_digest
89
+ @source_digest ||= Digest::MD5.hexdigest(File.read(name))
90
+ end
91
+
92
+ def missing_image_warning
93
+ "JPT Could not find #{name}. Your site will have broken images. Continuing."
71
94
  end
72
95
 
73
- def missing_image_error(source_name)
96
+ def missing_image_error
74
97
  <<~HEREDOC
75
- Jekyll Picture Tag could not find #{source_name}. You can force the
76
- build to continue anyway by setting "picture: ignore_missing_images:
77
- true" in "_config.yml". This setting can also accept a jekyll build
78
- environment, or an array of environments.
98
+ Could not find #{name}. You can force the build to continue anyway by
99
+ setting "picture: ignore_missing_images: true" in "_config.yml".
79
100
  HEREDOC
80
101
  end
81
102
  end
@@ -23,8 +23,9 @@ module PictureTag
23
23
  @format ||= files.first.format
24
24
  end
25
25
 
26
+ # GeneratedImage class
26
27
  def files
27
- @files ||= widths.collect { |w| generate_file(w) }
28
+ @files ||= build_files
28
29
  end
29
30
 
30
31
  def to_a
@@ -46,40 +47,74 @@ module PictureTag
46
47
  nil
47
48
  end
48
49
 
49
- # Check our source image size vs requested sizes
50
- def check_widths(targets)
51
- if targets.any? { |t| t > @source_image.width }
52
- handle_small_source(targets, @source_image.width)
53
- else
54
- targets
55
- end
56
- end
57
-
58
50
  # Generates an HTML attribute
59
51
  def media_attribute
60
52
  "(#{PictureTag.media_presets[@media]})"
61
53
  end
62
54
 
55
+ def width_attribute
56
+ files.first.source_width.to_s
57
+ end
58
+
59
+ def height_attribute
60
+ files.first.source_height.to_s
61
+ end
62
+
63
63
  private
64
64
 
65
- def handle_small_source(targets, image_width)
66
- Utils.warning(
67
- " #{@source_image.shortname} is #{image_width}px wide, smaller than" \
68
- " at least one size in the set #{targets}. Will not enlarge."
69
- )
65
+ def build_files
66
+ # By 'files', we mean the GeneratedImage class.
67
+ return target_files if target_files.all?(&:exists?)
68
+
69
+ # This triggers GeneratedImage to actually build an image file.
70
+ files = checked_targets
71
+ files.each(&:generate)
72
+
73
+ files
74
+ end
75
+
76
+ def checked_targets
77
+ if target_files.any? { |f| f.width > source_width }
78
+
79
+ small_source_warn
70
80
 
71
- small_targets = targets.dup.delete_if { |t| t >= image_width }
81
+ files = target_files.reject { |f| f.width >= source_width }
82
+ files.push(generate_file(source_width))
83
+ end
72
84
 
73
- small_targets.push image_width
85
+ files || target_files
86
+ end
74
87
 
75
- small_targets
88
+ def source_width
89
+ @source_width ||= if PictureTag.crop(@media)
90
+ target_files.first.source_width
91
+ else
92
+ @source_image.width
93
+ end
94
+ end
95
+
96
+ def target_files
97
+ @target_files ||= widths.collect { |w| generate_file(w) }
98
+ end
99
+
100
+ def small_source_warn
101
+ Utils.warning(
102
+ <<~HEREDOC
103
+ #{@source_image.shortname}
104
+ is #{source_width}px wide (after cropping, if applicable),
105
+ smaller than at least one size in the set #{widths}.
106
+ Will not enlarge.
107
+ HEREDOC
108
+ )
76
109
  end
77
110
 
78
111
  def generate_file(width)
79
112
  GeneratedImage.new(
80
113
  source_file: @source_image,
81
114
  width: width,
82
- format: @input_format
115
+ format: @input_format,
116
+ crop: PictureTag.crop(@media),
117
+ gravity: PictureTag.gravity(@media)
83
118
  )
84
119
  end
85
120
  end
@@ -6,11 +6,9 @@ module PictureTag
6
6
  private
7
7
 
8
8
  def widths
9
- target = PictureTag.preset['pixel_ratios'].collect do |p|
9
+ PictureTag.preset['pixel_ratios'].collect do |p|
10
10
  p * PictureTag.preset['base_width']
11
11
  end
12
-
13
- check_widths target
14
12
  end
15
13
 
16
14
  def build_srcset_entry(file)
@@ -22,7 +22,7 @@ module PictureTag
22
22
  private
23
23
 
24
24
  def widths
25
- check_widths PictureTag.widths(@media)
25
+ PictureTag.widths(@media)
26
26
  end
27
27
 
28
28
  def build_srcset_entry(file)