jekyll_picture_tag 1.10.1 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|