jekyll_picture_tag 1.10.1 → 1.14.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -0
  3. data/.github/workflows/code-checks.yml +43 -0
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +30 -0
  6. data/.ruby-version +1 -1
  7. data/docs/.envrc +2 -0
  8. data/docs/Gemfile +4 -2
  9. data/docs/Gemfile.lock +14 -12
  10. data/docs/_config.yml +6 -10
  11. data/docs/devs/contributing/code.md +46 -0
  12. data/docs/devs/contributing/docs.md +31 -0
  13. data/docs/devs/contributing/index.md +15 -0
  14. data/docs/devs/contributing/setup.md +33 -0
  15. data/docs/devs/contributing/testing.md +34 -0
  16. data/docs/devs/index.md +7 -0
  17. data/docs/{releases.md → devs/releases.md} +44 -15
  18. data/docs/index.md +42 -26
  19. data/docs/users/configuration/directories.md +34 -0
  20. data/docs/users/configuration/disable.md +24 -0
  21. data/docs/users/configuration/fast_build.md +28 -0
  22. data/docs/users/configuration/ignore_missing.md +23 -0
  23. data/docs/users/configuration/index.md +29 -0
  24. data/docs/users/configuration/kramdown_fix.md +20 -0
  25. data/docs/users/configuration/suppress_warnings.md +16 -0
  26. data/docs/users/configuration/urls.md +69 -0
  27. data/docs/users/index.md +7 -0
  28. data/docs/users/installation.md +52 -0
  29. data/docs/users/liquid_tag/argument_reference/alternate_images.md +18 -0
  30. data/docs/users/liquid_tag/argument_reference/attributes.md +42 -0
  31. data/docs/users/liquid_tag/argument_reference/base_image.md +12 -0
  32. data/docs/users/liquid_tag/argument_reference/crop.md +48 -0
  33. data/docs/users/liquid_tag/argument_reference/link.md +16 -0
  34. data/docs/users/liquid_tag/argument_reference/preset.md +17 -0
  35. data/docs/users/liquid_tag/argument_reference/readme.md +9 -0
  36. data/docs/users/liquid_tag/examples.md +93 -0
  37. data/docs/users/liquid_tag/index.md +31 -0
  38. data/docs/users/notes/git_lfs.md +7 -0
  39. data/docs/users/notes/github_pages.md +5 -0
  40. data/docs/users/notes/html_attributes.md +5 -0
  41. data/docs/users/notes/index.md +6 -0
  42. data/docs/users/notes/input_checking.md +6 -0
  43. data/docs/users/notes/kramdown_bug.md +41 -0
  44. data/docs/users/notes/managing_images.md +21 -0
  45. data/docs/{migration.md → users/notes/migration.md} +0 -0
  46. data/docs/users/presets/cropping.md +61 -0
  47. data/docs/users/presets/default.md +24 -0
  48. data/docs/users/presets/examples.md +79 -0
  49. data/docs/users/presets/fallback_image.md +28 -0
  50. data/docs/users/presets/html_attributes.md +26 -0
  51. data/docs/users/presets/image_formats.md +21 -0
  52. data/docs/users/presets/image_quality.md +105 -0
  53. data/docs/users/presets/index.md +101 -0
  54. data/docs/users/presets/link_source.md +16 -0
  55. data/docs/users/presets/markup_formats/fragments.md +48 -0
  56. data/docs/users/presets/markup_formats/javascript_friendly.md +57 -0
  57. data/docs/users/presets/markup_formats/readme.md +43 -0
  58. data/docs/users/presets/markup_formats/standard_html.md +25 -0
  59. data/docs/users/presets/media_queries.md +36 -0
  60. data/docs/users/presets/nomarkdown_override.md +17 -0
  61. data/docs/users/presets/pixel_ratio_srcsets.md +32 -0
  62. data/docs/users/presets/quality_width_graph.png +0 -0
  63. data/docs/users/presets/strip_metadata.md +13 -0
  64. data/docs/users/presets/width_height_attributes.md +34 -0
  65. data/docs/users/presets/width_srcsets.md +85 -0
  66. data/install_imagemagick.sh +23 -0
  67. data/jekyll_picture_tag.gemspec +10 -6
  68. data/lib/jekyll_picture_tag.rb +5 -5
  69. data/lib/jekyll_picture_tag/cache.rb +3 -0
  70. data/lib/jekyll_picture_tag/cache/base.rb +61 -0
  71. data/lib/jekyll_picture_tag/cache/generated.rb +20 -0
  72. data/lib/jekyll_picture_tag/cache/source.rb +19 -0
  73. data/lib/jekyll_picture_tag/defaults/global.yml +2 -0
  74. data/lib/jekyll_picture_tag/defaults/presets.yml +2 -0
  75. data/lib/jekyll_picture_tag/images.rb +3 -0
  76. data/lib/jekyll_picture_tag/images/generated_image.rb +130 -0
  77. data/lib/jekyll_picture_tag/{img_uri.rb → images/img_uri.rb} +4 -1
  78. data/lib/jekyll_picture_tag/images/source_image.rb +103 -0
  79. data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +3 -2
  80. data/lib/jekyll_picture_tag/instructions/preset.rb +24 -4
  81. data/lib/jekyll_picture_tag/instructions/set.rb +5 -1
  82. data/lib/jekyll_picture_tag/output_formats/basic.rb +16 -14
  83. data/lib/jekyll_picture_tag/output_formats/img.rb +11 -0
  84. data/lib/jekyll_picture_tag/output_formats/picture.rb +22 -0
  85. data/lib/jekyll_picture_tag/router.rb +3 -2
  86. data/lib/jekyll_picture_tag/srcsets/basic.rb +10 -1
  87. data/lib/jekyll_picture_tag/utils.rb +14 -0
  88. data/lib/jekyll_picture_tag/version.rb +1 -1
  89. data/readme.md +13 -10
  90. metadata +139 -46
  91. data/.travis.yml +0 -8
  92. data/docs/_layouts/directory.html +0 -32
  93. data/docs/assets/style.css +0 -31
  94. data/docs/contributing.md +0 -109
  95. data/docs/example_presets.md +0 -116
  96. data/docs/global_configuration.md +0 -173
  97. data/docs/installation.md +0 -45
  98. data/docs/notes.md +0 -91
  99. data/docs/output.md +0 -63
  100. data/docs/presets.md +0 -361
  101. data/docs/usage.md +0 -143
  102. data/lib/jekyll_picture_tag/generated_image.rb +0 -161
  103. data/lib/jekyll_picture_tag/source_image.rb +0 -87
@@ -0,0 +1,61 @@
1
+ require 'json'
2
+
3
+ module PictureTag
4
+ module Cache
5
+ # Basic image information cache functionality
6
+ module Base
7
+ def initialize(base_name)
8
+ @base_name = base_name
9
+ end
10
+
11
+ def [](key)
12
+ data[key]
13
+ end
14
+
15
+ def []=(key, value)
16
+ raise ArgumentError unless template.keys.include? key
17
+
18
+ data[key] = value
19
+ end
20
+
21
+ # Call after updating data.
22
+ def write
23
+ return if PictureTag.site.config['disable_disk_cache']
24
+
25
+ FileUtils.mkdir_p(File.join(base_directory, sub_directory))
26
+
27
+ File.open(filename, 'w+') do |f|
28
+ f.write JSON.generate(data)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def data
35
+ @data ||= if File.exist?(filename)
36
+ JSON.parse(File.read(filename)).transform_keys(&:to_sym)
37
+ else
38
+ template
39
+ end
40
+ end
41
+
42
+ # /home/dave/my_blog/.jekyll-cache/jpt/(cache_dir)/assets/myimage.jpg.json
43
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44
+ def base_directory
45
+ File.join(PictureTag.site.cache_dir, 'jpt', cache_dir)
46
+ end
47
+
48
+ # /home/dave/my_blog/.jekyll-cache/jpt/(cache_dir)/assets/myimage.jpg.json
49
+ # ^^^^^^^^
50
+ def sub_directory
51
+ File.dirname(@base_name)
52
+ end
53
+
54
+ # /home/dave/my_blog/.jekyll-cache/jpt/somefolder/myimage.jpg.json
55
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56
+ def filename
57
+ File.join(base_directory, @base_name + '.json')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ module PictureTag
2
+ module Cache
3
+ # Caches generated image details, so we can skip expensive operations whenever
4
+ # possible.
5
+ # Stored width and height are values for the source image, after cropping.
6
+ class Generated
7
+ include Base
8
+
9
+ private
10
+
11
+ def cache_dir
12
+ 'generated'
13
+ end
14
+
15
+ def template
16
+ { width: nil, height: nil }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -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
@@ -9,3 +9,5 @@ picture:
9
9
  ignore_missing_images: false
10
10
  disabled: false
11
11
  fast_build: false
12
+ ignore_baseurl: false
13
+ baseurl_key: 'baseurl'
@@ -8,3 +8,5 @@ link_source: false
8
8
  quality: 75
9
9
  data_sizes: true
10
10
  gravity: center
11
+ dimension_attributes: false
12
+ strip_metadata: true
@@ -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,130 @@
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
+ def image
85
+ return @image if defined? @image
86
+
87
+ # Post crop, before resizing and reformatting
88
+ @source_dimensions = { width: image_base.width, height: image_base.height }
89
+
90
+ @image = image_base
91
+ end
92
+
93
+ def image_base
94
+ @image_base ||= Image.open(@source.name).combine_options do |i|
95
+ if PictureTag.preset['strip_metadata']
96
+ i.auto_orient
97
+ i.strip
98
+ end
99
+
100
+ if @crop
101
+ i.gravity @gravity
102
+ i.crop @crop
103
+ end
104
+ end
105
+ end
106
+
107
+ def generate_image
108
+ puts 'Generating new image file: ' + name
109
+
110
+ image.format(@format, 0, { resize: "#{@width}x", quality: quality })
111
+ FileUtils.mkdir_p(File.dirname(absolute_filename))
112
+
113
+ image.write absolute_filename
114
+
115
+ FileUtils.chmod(0o644, absolute_filename)
116
+ end
117
+
118
+ def quality
119
+ PictureTag.quality(@format, @width)
120
+ end
121
+
122
+ def process_format(format)
123
+ if format.casecmp('original').zero?
124
+ @source.ext
125
+ else
126
+ format.downcase
127
+ end
128
+ end
129
+ end
130
+ 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
@@ -40,7 +41,9 @@ module PictureTag
40
41
  # ^^^^^^^^^^^^^
41
42
  # | domain | baseurl | directory | filename
42
43
  def baseurl
43
- PictureTag.config['baseurl'] || ''
44
+ return '' if PictureTag.pconfig['ignore_baseurl']
45
+
46
+ PictureTag.config[PictureTag.pconfig['baseurl_key']] || ''
44
47
  end
45
48
 
46
49
  # https://example.com/my-base-path/assets/generated-images/image.jpg
@@ -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
@@ -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