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.
- checksums.yaml +4 -4
- data/.envrc +2 -0
- data/.github/workflows/code-checks.yml +43 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +30 -0
- data/.ruby-version +1 -1
- data/docs/.envrc +2 -0
- data/docs/Gemfile +4 -2
- data/docs/Gemfile.lock +14 -12
- data/docs/_config.yml +6 -10
- data/docs/devs/contributing/code.md +46 -0
- data/docs/devs/contributing/docs.md +31 -0
- data/docs/devs/contributing/index.md +15 -0
- data/docs/devs/contributing/setup.md +33 -0
- data/docs/devs/contributing/testing.md +34 -0
- data/docs/devs/index.md +7 -0
- data/docs/{releases.md → devs/releases.md} +44 -15
- data/docs/index.md +42 -26
- data/docs/users/configuration/directories.md +34 -0
- data/docs/users/configuration/disable.md +24 -0
- data/docs/users/configuration/fast_build.md +28 -0
- data/docs/users/configuration/ignore_missing.md +23 -0
- data/docs/users/configuration/index.md +29 -0
- data/docs/users/configuration/kramdown_fix.md +20 -0
- data/docs/users/configuration/suppress_warnings.md +16 -0
- data/docs/users/configuration/urls.md +69 -0
- data/docs/users/index.md +7 -0
- data/docs/users/installation.md +52 -0
- data/docs/users/liquid_tag/argument_reference/alternate_images.md +18 -0
- data/docs/users/liquid_tag/argument_reference/attributes.md +42 -0
- data/docs/users/liquid_tag/argument_reference/base_image.md +12 -0
- data/docs/users/liquid_tag/argument_reference/crop.md +48 -0
- data/docs/users/liquid_tag/argument_reference/link.md +16 -0
- data/docs/users/liquid_tag/argument_reference/preset.md +17 -0
- data/docs/users/liquid_tag/argument_reference/readme.md +9 -0
- data/docs/users/liquid_tag/examples.md +93 -0
- data/docs/users/liquid_tag/index.md +31 -0
- data/docs/users/notes/git_lfs.md +7 -0
- data/docs/users/notes/github_pages.md +5 -0
- data/docs/users/notes/html_attributes.md +5 -0
- data/docs/users/notes/index.md +6 -0
- data/docs/users/notes/input_checking.md +6 -0
- data/docs/users/notes/kramdown_bug.md +41 -0
- data/docs/users/notes/managing_images.md +21 -0
- data/docs/{migration.md → users/notes/migration.md} +0 -0
- data/docs/users/presets/cropping.md +61 -0
- data/docs/users/presets/default.md +24 -0
- data/docs/users/presets/examples.md +79 -0
- data/docs/users/presets/fallback_image.md +28 -0
- data/docs/users/presets/html_attributes.md +26 -0
- data/docs/users/presets/image_formats.md +21 -0
- data/docs/users/presets/image_quality.md +105 -0
- data/docs/users/presets/index.md +101 -0
- data/docs/users/presets/link_source.md +16 -0
- data/docs/users/presets/markup_formats/fragments.md +48 -0
- data/docs/users/presets/markup_formats/javascript_friendly.md +57 -0
- data/docs/users/presets/markup_formats/readme.md +43 -0
- data/docs/users/presets/markup_formats/standard_html.md +25 -0
- data/docs/users/presets/media_queries.md +36 -0
- data/docs/users/presets/nomarkdown_override.md +17 -0
- data/docs/users/presets/pixel_ratio_srcsets.md +32 -0
- data/docs/users/presets/quality_width_graph.png +0 -0
- data/docs/users/presets/strip_metadata.md +13 -0
- data/docs/users/presets/width_height_attributes.md +34 -0
- data/docs/users/presets/width_srcsets.md +85 -0
- data/install_imagemagick.sh +23 -0
- data/jekyll_picture_tag.gemspec +10 -6
- data/lib/jekyll_picture_tag.rb +5 -5
- data/lib/jekyll_picture_tag/cache.rb +3 -0
- data/lib/jekyll_picture_tag/cache/base.rb +61 -0
- data/lib/jekyll_picture_tag/cache/generated.rb +20 -0
- data/lib/jekyll_picture_tag/cache/source.rb +19 -0
- data/lib/jekyll_picture_tag/defaults/global.yml +2 -0
- data/lib/jekyll_picture_tag/defaults/presets.yml +2 -0
- data/lib/jekyll_picture_tag/images.rb +3 -0
- data/lib/jekyll_picture_tag/images/generated_image.rb +130 -0
- data/lib/jekyll_picture_tag/{img_uri.rb → images/img_uri.rb} +4 -1
- data/lib/jekyll_picture_tag/images/source_image.rb +103 -0
- data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +3 -2
- data/lib/jekyll_picture_tag/instructions/preset.rb +24 -4
- data/lib/jekyll_picture_tag/instructions/set.rb +5 -1
- data/lib/jekyll_picture_tag/output_formats/basic.rb +16 -14
- data/lib/jekyll_picture_tag/output_formats/img.rb +11 -0
- data/lib/jekyll_picture_tag/output_formats/picture.rb +22 -0
- data/lib/jekyll_picture_tag/router.rb +3 -2
- data/lib/jekyll_picture_tag/srcsets/basic.rb +10 -1
- data/lib/jekyll_picture_tag/utils.rb +14 -0
- data/lib/jekyll_picture_tag/version.rb +1 -1
- data/readme.md +13 -10
- metadata +139 -46
- data/.travis.yml +0 -8
- data/docs/_layouts/directory.html +0 -32
- data/docs/assets/style.css +0 -31
- data/docs/contributing.md +0 -109
- data/docs/example_presets.md +0 -116
- data/docs/global_configuration.md +0 -173
- data/docs/installation.md +0 -45
- data/docs/notes.md +0 -91
- data/docs/output.md +0 -63
- data/docs/presets.md +0 -361
- data/docs/usage.md +0 -143
- data/lib/jekyll_picture_tag/generated_image.rb +0 -161
- 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
|
@@ -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.
|
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
|
@@ -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
|
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',
|
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
|
-
|
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
|