jekyll_picture_tag 1.10.0 → 1.13.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +79 -0
  3. data/docs/Gemfile +4 -2
  4. data/docs/Gemfile.lock +14 -12
  5. data/docs/_config.yml +6 -10
  6. data/docs/devs/contributing/code.md +44 -0
  7. data/docs/devs/contributing/docs.md +13 -0
  8. data/docs/devs/contributing/index.md +15 -0
  9. data/docs/devs/contributing/setup.md +13 -0
  10. data/docs/devs/contributing/testing.md +41 -0
  11. data/docs/devs/index.md +7 -0
  12. data/docs/{releases.md → devs/releases.md} +37 -13
  13. data/docs/index.md +42 -26
  14. data/docs/users/configuration/cdn.md +35 -0
  15. data/docs/users/configuration/directories.md +34 -0
  16. data/docs/users/configuration/disable.md +24 -0
  17. data/docs/users/configuration/fast_build.md +28 -0
  18. data/docs/users/configuration/ignore_missing.md +23 -0
  19. data/docs/users/configuration/index.md +29 -0
  20. data/docs/users/configuration/kramdown_fix.md +20 -0
  21. data/docs/users/configuration/relative_urls.md +15 -0
  22. data/docs/users/configuration/suppress_warnings.md +16 -0
  23. data/docs/users/index.md +7 -0
  24. data/docs/users/installation.md +52 -0
  25. data/docs/users/liquid_tag/argument_reference/alternate_images.md +18 -0
  26. data/docs/users/liquid_tag/argument_reference/attributes.md +42 -0
  27. data/docs/users/liquid_tag/argument_reference/base_image.md +12 -0
  28. data/docs/users/liquid_tag/argument_reference/crop.md +48 -0
  29. data/docs/users/liquid_tag/argument_reference/link.md +16 -0
  30. data/docs/users/liquid_tag/argument_reference/preset.md +17 -0
  31. data/docs/users/liquid_tag/argument_reference/readme.md +9 -0
  32. data/docs/users/liquid_tag/examples.md +93 -0
  33. data/docs/users/liquid_tag/index.md +31 -0
  34. data/docs/users/notes/git_lfs.md +7 -0
  35. data/docs/users/notes/github_pages.md +5 -0
  36. data/docs/users/notes/html_attributes.md +5 -0
  37. data/docs/users/notes/index.md +6 -0
  38. data/docs/users/notes/input_checking.md +6 -0
  39. data/docs/users/notes/kramdown_bug.md +41 -0
  40. data/docs/users/notes/managing_images.md +21 -0
  41. data/docs/{migration.md → users/notes/migration.md} +0 -0
  42. data/docs/users/presets/cropping.md +61 -0
  43. data/docs/users/presets/default.md +23 -0
  44. data/docs/users/presets/examples.md +79 -0
  45. data/docs/users/presets/fallback_image.md +28 -0
  46. data/docs/users/presets/html_attributes.md +26 -0
  47. data/docs/users/presets/image_formats.md +21 -0
  48. data/docs/users/presets/image_quality.md +105 -0
  49. data/docs/users/presets/index.md +101 -0
  50. data/docs/users/presets/link_source.md +16 -0
  51. data/docs/users/presets/markup_formats/fragments.md +48 -0
  52. data/docs/users/presets/markup_formats/javascript_friendly.md +57 -0
  53. data/docs/users/presets/markup_formats/readme.md +43 -0
  54. data/docs/users/presets/markup_formats/standard_html.md +25 -0
  55. data/docs/users/presets/media_queries.md +36 -0
  56. data/docs/users/presets/nomarkdown_override.md +17 -0
  57. data/docs/users/presets/pixel_ratio_srcsets.md +32 -0
  58. data/docs/users/presets/quality_width_graph.png +0 -0
  59. data/docs/users/presets/width_height_attributes.md +34 -0
  60. data/docs/users/presets/width_srcsets.md +85 -0
  61. data/jekyll_picture_tag.gemspec +4 -5
  62. data/lib/jekyll_picture_tag.rb +3 -4
  63. data/lib/jekyll_picture_tag/cache.rb +3 -0
  64. data/lib/jekyll_picture_tag/cache/base.rb +59 -0
  65. data/lib/jekyll_picture_tag/cache/generated.rb +20 -0
  66. data/lib/jekyll_picture_tag/cache/source.rb +19 -0
  67. data/lib/jekyll_picture_tag/defaults/presets.yml +1 -0
  68. data/lib/jekyll_picture_tag/images.rb +3 -0
  69. data/lib/jekyll_picture_tag/images/generated_image.rb +127 -0
  70. data/lib/jekyll_picture_tag/{img_uri.rb → images/img_uri.rb} +1 -0
  71. data/lib/jekyll_picture_tag/images/source_image.rb +103 -0
  72. data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +3 -2
  73. data/lib/jekyll_picture_tag/instructions/configuration.rb +1 -1
  74. data/lib/jekyll_picture_tag/instructions/preset.rb +24 -4
  75. data/lib/jekyll_picture_tag/instructions/set.rb +5 -1
  76. data/lib/jekyll_picture_tag/output_formats/basic.rb +16 -14
  77. data/lib/jekyll_picture_tag/output_formats/img.rb +11 -0
  78. data/lib/jekyll_picture_tag/output_formats/picture.rb +22 -0
  79. data/lib/jekyll_picture_tag/router.rb +3 -2
  80. data/lib/jekyll_picture_tag/srcsets/basic.rb +10 -1
  81. data/lib/jekyll_picture_tag/utils.rb +14 -0
  82. data/lib/jekyll_picture_tag/version.rb +1 -1
  83. data/readme.md +9 -7
  84. metadata +78 -45
  85. data/docs/_layouts/directory.html +0 -32
  86. data/docs/assets/style.css +0 -31
  87. data/docs/contributing.md +0 -109
  88. data/docs/example_presets.md +0 -116
  89. data/docs/global_configuration.md +0 -173
  90. data/docs/installation.md +0 -45
  91. data/docs/notes.md +0 -91
  92. data/docs/output.md +0 -63
  93. data/docs/presets.md +0 -361
  94. data/docs/usage.md +0 -143
  95. data/lib/jekyll_picture_tag/generated_image.rb +0 -161
  96. data/lib/jekyll_picture_tag/source_image.rb +0 -87
@@ -0,0 +1,19 @@
1
+ module PictureTag
2
+ module Cache
3
+ # Caches source image details, so we can skip expensive operations whenever
4
+ # possible.
5
+ class Source
6
+ include Base
7
+
8
+ private
9
+
10
+ def template
11
+ { digest: nil, width: nil, height: nil }
12
+ end
13
+
14
+ def cache_dir
15
+ 'source'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -8,3 +8,4 @@ link_source: false
8
8
  quality: 75
9
9
  data_sizes: true
10
10
  gravity: center
11
+ dimension_attributes: false
@@ -0,0 +1,3 @@
1
+ require_relative 'images/generated_image'
2
+ require_relative 'images/img_uri'
3
+ require_relative 'images/source_image'
@@ -0,0 +1,127 @@
1
+ require 'mini_magick'
2
+
3
+ module PictureTag
4
+ # Represents a generated image file.
5
+ class GeneratedImage
6
+ attr_reader :width, :format
7
+
8
+ include MiniMagick
9
+
10
+ def initialize(source_file:, width:, format:, crop: nil, gravity: '')
11
+ @source = source_file
12
+ @width = width
13
+ @format = process_format format
14
+ @crop = crop
15
+ @gravity = gravity
16
+ end
17
+
18
+ def exists?
19
+ File.exist?(absolute_filename)
20
+ end
21
+
22
+ def generate
23
+ generate_image unless @source.missing || exists?
24
+ end
25
+
26
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
27
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28
+ def absolute_filename
29
+ @absolute_filename ||= File.join(PictureTag.dest_dir, name)
30
+ end
31
+
32
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
33
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34
+ def name
35
+ @name ||= "#{@source.base_name}-#{@width}-#{id}.#{@format}"
36
+ end
37
+
38
+ # https://example.com/assets/somefolder/myimage-100-123abc.jpg
39
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40
+ def uri
41
+ ImgURI.new(name).to_s
42
+ end
43
+
44
+ # Post crop
45
+ def source_width
46
+ update_cache unless cache[:width]
47
+
48
+ cache[:width]
49
+ end
50
+
51
+ # Post crop
52
+ def source_height
53
+ update_cache unless cache[:height]
54
+
55
+ cache[:height]
56
+ end
57
+
58
+ private
59
+
60
+ # We exclude width and format from the cache name, since it isn't specific to them.
61
+ def cache
62
+ @cache ||= Cache::Generated.new("#{@source.base_name}-#{id}")
63
+ end
64
+
65
+ def update_cache
66
+ return if @source.missing
67
+
68
+ # Ensure it's generated:
69
+ image
70
+
71
+ cache[:width] = @source_dimensions[:width]
72
+ cache[:height] = @source_dimensions[:height]
73
+
74
+ cache.write
75
+ end
76
+
77
+ # Hash all inputs and truncate, so we know when they change without getting too long.
78
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-1234abcde.jpg
79
+ # ^^^^^^^^^
80
+ def id
81
+ @id ||= Digest::MD5.hexdigest([@source.digest, @crop, @gravity, quality].join)[0..8]
82
+ end
83
+
84
+ # Post crop, before resizing and reformatting
85
+ def image
86
+ @image ||= open_image
87
+ end
88
+
89
+ def open_image
90
+ image_base = Image.open(@source.name)
91
+ image_base.combine_options do |i|
92
+ i.auto_orient
93
+ if @crop
94
+ i.gravity @gravity
95
+ i.crop @crop
96
+ end
97
+ end
98
+
99
+ @source_dimensions = { width: image_base.width, height: image_base.height }
100
+
101
+ image_base
102
+ end
103
+
104
+ def generate_image
105
+ puts 'Generating new image file: ' + name
106
+
107
+ image.format(@format, 0, { resize: "#{@width}x", quality: quality })
108
+ FileUtils.mkdir_p(File.dirname(absolute_filename))
109
+
110
+ image.write absolute_filename
111
+
112
+ FileUtils.chmod(0o644, absolute_filename)
113
+ end
114
+
115
+ def quality
116
+ PictureTag.quality(@format, @width)
117
+ end
118
+
119
+ def process_format(format)
120
+ if format.casecmp('original').zero?
121
+ @source.ext
122
+ else
123
+ format.downcase
124
+ end
125
+ end
126
+ end
127
+ end
@@ -8,6 +8,7 @@ module PictureTag
8
8
  # image. Call to_s on it to get the link.
9
9
  class ImgURI
10
10
  attr_reader :filename, :source_image
11
+
11
12
  def initialize(filename, source_image: false)
12
13
  @source_image = source_image
13
14
  @filename = filename
@@ -0,0 +1,103 @@
1
+ module PictureTag
2
+ # Handles a given source image file and its properties. Provides a speed
3
+ # advantage by storing expensive file reads and writes in instance variables,
4
+ # to be reused by many different generated images.
5
+ class SourceImage
6
+ attr_reader :shortname, :missing, :media_preset
7
+
8
+ include MiniMagick
9
+
10
+ def initialize(relative_filename, media_preset = nil)
11
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
12
+ # ^^^^^^^^^^^^^^^^^^^^^^
13
+ @shortname = relative_filename
14
+ @media_preset = media_preset
15
+
16
+ @missing = missing?
17
+ check_cache
18
+ end
19
+
20
+ def digest
21
+ @digest ||= cache[:digest] || ''
22
+ end
23
+
24
+ def width
25
+ @width ||= cache[:width] || 999_999
26
+ end
27
+
28
+ def height
29
+ @height ||= cache[:height] || 999_999
30
+ end
31
+
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
+ # ^^^^^^^^^^^^^^^^^^
40
+ def base_name
41
+ @shortname.delete_suffix File.extname(@shortname)
42
+ end
43
+
44
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
45
+ # ^^^
46
+ def ext
47
+ @ext ||= File.extname(name)[1..-1].downcase
48
+ end
49
+
50
+ private
51
+
52
+ def cache
53
+ @cache ||= Cache::Source.new(@shortname)
54
+ end
55
+
56
+ def missing?
57
+ if File.exist? name
58
+ false
59
+
60
+ elsif PictureTag.continue_on_missing?
61
+ Utils.warning(missing_image_warning)
62
+ true
63
+
64
+ else
65
+ raise ArgumentError, missing_image_error
66
+ end
67
+ end
68
+
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]
74
+ end
75
+
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."
94
+ end
95
+
96
+ def missing_image_error
97
+ <<~HEREDOC
98
+ Could not find #{name}. You can force the build to continue anyway by
99
+ setting "picture: ignore_missing_images: true" in "_config.yml".
100
+ HEREDOC
101
+ end
102
+ end
103
+ end
@@ -51,9 +51,10 @@ module PictureTag
51
51
  end
52
52
 
53
53
  def handle_special(char)
54
- if char == '\\'
54
+ case char
55
+ when '\\'
55
56
  @escaped = true
56
- elsif char == '"'
57
+ when '"'
57
58
  @in_quotes = !@in_quotes
58
59
  @word << char
59
60
  end
@@ -30,7 +30,7 @@ module PictureTag
30
30
  # source_dest is the jekyll-picture-tag destination directory. (generated
31
31
  # file location setting.)
32
32
  def dest_dir
33
- File.join PictureTag.site.dest, pconfig['output']
33
+ File.join PictureTag.site.config['destination'], pconfig['output']
34
34
  end
35
35
 
36
36
  def nomarkdown?
@@ -3,6 +3,7 @@ module PictureTag
3
3
  # Handles the specific tag image set to construct.
4
4
  class Preset
5
5
  attr_reader :name
6
+
6
7
  def initialize(name)
7
8
  @name = name
8
9
  @content = build_preset
@@ -39,9 +40,24 @@ module PictureTag
39
40
  setting_lookup('widths', 'media', media)
40
41
  end
41
42
 
42
- # Image quality setting, possibly dependent on format.
43
- def quality(format = nil)
44
- setting_lookup('quality', 'format', format)
43
+ # Image quality setting. Surprisingly complicated; can depend on both format and width.
44
+ def quality(format = nil, width = nil)
45
+ setting = setting_lookup('quality', 'format', format)
46
+
47
+ return setting unless setting.is_a? Hash
48
+
49
+ parse_quality_hash(setting, width)
50
+ end
51
+
52
+ def parse_quality_hash(points, width)
53
+ # The points can be given in any order.
54
+ low, high = *points.keys.map(&:to_i).sort
55
+
56
+ case width
57
+ when 0..low then points[low]
58
+ when low..high then Utils.interpolate(points.keys, points.values, width)
59
+ when high..999_999 then points[high]
60
+ end
45
61
  end
46
62
 
47
63
  # Gravity setting (for imagemagick cropping)
@@ -80,9 +96,13 @@ module PictureTag
80
96
  end
81
97
 
82
98
  def grab_data_file
99
+ search_data('presets') || search_data('markup_presets') || no_preset
100
+ end
101
+
102
+ def search_data(key)
83
103
  PictureTag.site
84
104
  .data
85
- .dig('picture', 'markup_presets', @name) || no_preset
105
+ .dig('picture', key, @name)
86
106
  end
87
107
 
88
108
  def no_preset
@@ -27,7 +27,7 @@ module PictureTag
27
27
  # These are our Media Query presets. It's really just a hash, and there
28
28
  # are no default values, so extracting this to its own class is overkill.
29
29
  def media_presets
30
- PictureTag.site.data.dig('picture', 'media_presets') || {}
30
+ search_data('media_queries') || search_data('media_presets') || {}
31
31
  end
32
32
 
33
33
  def source_images
@@ -52,6 +52,10 @@ module PictureTag
52
52
 
53
53
  private
54
54
 
55
+ def search_data(key)
56
+ PictureTag.site.data.dig('picture', key)
57
+ end
58
+
55
59
  def build_source_images
56
60
  source_names = params.source_names
57
61
  media_presets = params.media_presets
@@ -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
@@ -73,24 +73,26 @@ module PictureTag
73
73
  def build_fallback_image
74
74
  return fallback_candidate if fallback_candidate.exists?
75
75
 
76
- build_new_fallback_image
77
- end
78
-
79
- def fallback_candidate
80
- @fallback_candidate ||= GeneratedImage.new(
76
+ image = GeneratedImage.new(
81
77
  source_file: PictureTag.source_images.first,
82
78
  format: PictureTag.fallback_format,
83
- width: PictureTag.fallback_width,
79
+ width: checked_fallback_width,
84
80
  crop: PictureTag.crop,
85
81
  gravity: PictureTag.gravity
86
82
  )
83
+
84
+ image.generate
85
+
86
+ image
87
87
  end
88
88
 
89
- def build_new_fallback_image
90
- GeneratedImage.new(
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(
91
93
  source_file: PictureTag.source_images.first,
92
94
  format: PictureTag.fallback_format,
93
- width: checked_fallback_width,
95
+ width: PictureTag.fallback_width,
94
96
  crop: PictureTag.crop,
95
97
  gravity: PictureTag.gravity
96
98
  )
@@ -119,7 +121,7 @@ module PictureTag
119
121
 
120
122
  def source_width
121
123
  if PictureTag.crop
122
- fallback_candidate.cropped_source_width
124
+ fallback_candidate.source_width
123
125
  else
124
126
  source.width
125
127
  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