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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/.travis.yml +4 -7
- data/Dockerfile +9 -0
- data/docs/Gemfile.lock +183 -88
- data/docs/contributing.md +50 -16
- data/docs/example_presets.md +1 -1
- data/docs/global_configuration.md +55 -2
- data/docs/index.md +27 -21
- data/docs/installation.md +22 -7
- data/docs/presets.md +137 -55
- data/docs/releases.md +20 -1
- data/docs/usage.md +83 -39
- data/jekyll_picture_tag.gemspec +1 -1
- data/lib/jekyll_picture_tag.rb +28 -10
- data/lib/jekyll_picture_tag/cache.rb +3 -0
- data/lib/jekyll_picture_tag/cache/base.rb +59 -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/generated_image.rb +85 -20
- data/lib/jekyll_picture_tag/img_uri.rb +1 -0
- data/lib/jekyll_picture_tag/instructions.rb +1 -0
- data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +69 -0
- data/lib/jekyll_picture_tag/instructions/configuration.rb +47 -11
- data/lib/jekyll_picture_tag/instructions/preset.rb +35 -14
- data/lib/jekyll_picture_tag/instructions/set.rb +18 -8
- data/lib/jekyll_picture_tag/instructions/tag_parser.rb +59 -69
- data/lib/jekyll_picture_tag/output_formats/basic.rb +42 -11
- 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 +17 -0
- data/lib/jekyll_picture_tag/source_image.rb +60 -39
- data/lib/jekyll_picture_tag/srcsets/basic.rb +54 -19
- data/lib/jekyll_picture_tag/srcsets/pixel_ratio.rb +1 -3
- data/lib/jekyll_picture_tag/srcsets/width.rb +1 -1
- data/lib/jekyll_picture_tag/utils.rb +18 -0
- data/lib/jekyll_picture_tag/version.rb +1 -1
- data/readme.md +40 -16
- 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
|
-
#
|
|
72
|
+
# GeneratedImage class, not HTML
|
|
73
73
|
def build_fallback_image
|
|
74
|
-
|
|
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:
|
|
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 >
|
|
133
|
+
if target > source_width
|
|
103
134
|
Utils.warning "#{source.shortname} is smaller than the " \
|
|
104
|
-
"requested fallback width of #{target}px. Using #{
|
|
135
|
+
"requested fallback width of #{target}px. Using #{source_width}" \
|
|
105
136
|
' px instead.'
|
|
106
|
-
|
|
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 :
|
|
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 ||=
|
|
17
|
-
999_999
|
|
18
|
-
else
|
|
19
|
-
image.width
|
|
20
|
-
end
|
|
25
|
+
@width ||= cache[:width] || 999_999
|
|
21
26
|
end
|
|
22
27
|
|
|
23
|
-
def
|
|
24
|
-
@
|
|
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
|
-
#
|
|
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
|
-
#
|
|
44
|
+
# /home/dave/my_blog/assets/images/somefolder/myimage.jpg
|
|
45
|
+
# ^^^
|
|
37
46
|
def ext
|
|
38
|
-
|
|
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
|
|
45
|
-
@
|
|
52
|
+
def cache
|
|
53
|
+
@cache ||= Cache::Source.new(@shortname)
|
|
46
54
|
end
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
57
|
-
|
|
61
|
+
Utils.warning(missing_image_warning)
|
|
62
|
+
true
|
|
58
63
|
|
|
59
64
|
else
|
|
60
|
-
raise ArgumentError, missing_image_error
|
|
65
|
+
raise ArgumentError, missing_image_error
|
|
61
66
|
end
|
|
67
|
+
end
|
|
62
68
|
|
|
63
|
-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
96
|
+
def missing_image_error
|
|
74
97
|
<<~HEREDOC
|
|
75
|
-
|
|
76
|
-
|
|
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 ||=
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
81
|
+
files = target_files.reject { |f| f.width >= source_width }
|
|
82
|
+
files.push(generate_file(source_width))
|
|
83
|
+
end
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
files || target_files
|
|
86
|
+
end
|
|
74
87
|
|
|
75
|
-
|
|
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
|
-
|
|
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)
|