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