jekyll_picture_tag 1.8.0 → 1.11.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.
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)