jekyll_picture_tag 1.9.0 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- 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/index.md +26 -20
- data/docs/installation.md +22 -7
- data/docs/presets.md +106 -54
- data/docs/releases.md +4 -0
- data/docs/usage.md +68 -38
- data/jekyll_picture_tag.gemspec +2 -1
- data/lib/jekyll_picture_tag/defaults/presets.yml +1 -0
- data/lib/jekyll_picture_tag/generated_image.rb +47 -15
- data/lib/jekyll_picture_tag/instructions.rb +1 -0
- data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +68 -0
- data/lib/jekyll_picture_tag/instructions/preset.rb +34 -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 +28 -11
- data/lib/jekyll_picture_tag/router.rb +8 -0
- data/lib/jekyll_picture_tag/srcsets/basic.rb +20 -7
- data/lib/jekyll_picture_tag/utils.rb +18 -0
- data/lib/jekyll_picture_tag/version.rb +1 -1
- data/readme.md +35 -16
- metadata +21 -5
data/docs/releases.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
---
|
3
3
|
# Release History
|
4
4
|
|
5
|
+
* 1.10.0 May 11, 2020
|
6
|
+
* **Image Cropping support!** access the power of ImageMagick's `crop` function.
|
7
|
+
* Don't issue a warning when `default` preset is not found.
|
8
|
+
* Documentation improvements
|
5
9
|
* 1.9.0 Feb 2, 2020
|
6
10
|
* Add `fast_build` global setting
|
7
11
|
* Add `disabled` global setting
|
data/docs/usage.md
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
---
|
2
2
|
---
|
3
|
+
|
3
4
|
# How to use the Liquid Tag:
|
4
5
|
|
5
6
|
## Format:
|
6
7
|
|
7
8
|
{% raw %}
|
8
|
-
`{% picture [preset] (
|
9
|
+
`{% picture [preset] (image) [crop] [alternate images & crops] [attributes] %}`
|
9
10
|
{% endraw %}
|
10
11
|
|
11
12
|
The only required argument is the base image. Line breaks and extra spaces are fine, and you can
|
@@ -16,50 +17,79 @@ use liquid variables anywhere.
|
|
16
17
|
{% raw %}
|
17
18
|
`{% picture example.jpg %}`
|
18
19
|
|
19
|
-
`{% picture thumbnail example.jpg --alt Example Image %}`
|
20
|
+
`{% picture thumbnail example.jpg 1:1 --alt Example Image %}`
|
20
21
|
|
21
|
-
`{% picture example.jpg --picture class="attribute-demo" %}`
|
22
|
+
`{% picture example.jpg 16:9 north --picture class="attribute-demo" %}`
|
22
23
|
|
23
24
|
`{% picture blog_index {{ post.image }} --link {{ post.url }} %}`
|
24
25
|
|
25
26
|
`{% picture "some example.jpg" mobile: other\ example.jpg %}`
|
26
27
|
|
27
28
|
```md
|
28
|
-
{% picture
|
29
|
-
hero
|
30
|
-
example.jpg
|
31
|
-
tablet: example_cropped.jpg
|
32
|
-
mobile: example_cropped_more.jpg
|
33
|
-
--alt Happy Puppy
|
34
|
-
--picture class="hero"
|
29
|
+
{% picture
|
30
|
+
hero
|
31
|
+
example.jpg 16:9 east
|
32
|
+
tablet: example_cropped.jpg 3:2 east
|
33
|
+
mobile: example_cropped_more.jpg 1:1-50+0 east
|
34
|
+
--alt Happy Puppy
|
35
|
+
--picture class="hero"
|
35
36
|
--link /
|
36
37
|
%}
|
37
38
|
```
|
39
|
+
|
38
40
|
{% endraw %}
|
39
41
|
|
40
42
|
## Argument reference
|
41
43
|
|
42
44
|
Given in order:
|
43
45
|
|
44
|
-
|
46
|
+
- **Preset**
|
45
47
|
|
46
48
|
Select a [markup preset]({{ site.baseurl }}/presets#markup-presets), or omit to use the `default` preset. Presets
|
47
49
|
are collections of settings that determine nearly everything about JPT's output, from the image
|
48
50
|
formats used to the exact format your markup will take.
|
49
51
|
|
50
|
-
|
52
|
+
- **Base Image** (Required)
|
51
53
|
|
52
54
|
Can be any raster image (as long as you have the required ImageMagick delegate). Relative to
|
53
55
|
jekyll's root directory, or the `source` [setting]({{ site.baseurl }}/global_configuration) if you've configured it.
|
54
56
|
|
55
|
-
For filenames with spaces, either use double quotes (`"my image.jpg"`) or a backslash (`my\
|
56
|
-
|
57
|
+
For filenames with spaces, either use double quotes (`"my image.jpg"`) or a backslash (`my\ image.jpg`).
|
58
|
+
|
59
|
+
- **Crop**
|
60
|
+
|
61
|
+
**Check the [ installation guide ](installation) before using this feature.**
|
62
|
+
|
63
|
+
Crop an image to a given aspect ratio or size. This argument is given as a `geometry` and
|
64
|
+
(optionally) a `gravity`, which can appear in either order and are thin wrappers around
|
65
|
+
ImageMagick's [geometry](http://www.imagemagick.org/script/command-line-processing.php#geometry)
|
66
|
+
and [gravity](http://www.imagemagick.org/script/command-line-options.php#gravity) settings. The
|
67
|
+
values given here will override the preset settings (if present), can be given after every image,
|
68
|
+
and apply only to the preceding image.
|
69
|
+
|
70
|
+
Geometry can take many forms, but most likely you'll want to set an aspect ratio-- given in the
|
71
|
+
standard `width:height` ratio such as `3:2`. Gravity sets which portion of the image to keep, and
|
72
|
+
is given in compass directions (`north`, `southeast`, etc) or `center` (default). Cropping happens
|
73
|
+
before resizing; the preset `widths` setting is a post-crop value.
|
57
74
|
|
58
|
-
|
75
|
+
If you'd like more fine-grained control, this can be offset by appending `+|-x` and (optionally)
|
76
|
+
`y` pixel values to the _geometry_ (not the gravity!). Example: `1:1+400 west` means "Crop to a
|
77
|
+
1:1 aspect ratio, starting 400 pixels from the left side.", and `north 3:2+0+100` means "Crop to
|
78
|
+
3:2, starting 100 pixels from the top." These can get a bit persnickety; there's nothing to stop
|
79
|
+
you from running off the side of the image. Pay attention.
|
59
80
|
|
60
|
-
|
81
|
+
For detailed documentation, see ImageMagick's
|
82
|
+
[crop](http://www.imagemagick.org/script/command-line-options.php#crop) tool.
|
61
83
|
|
62
|
-
|
84
|
+
_Note:_ If you do a lot of trial and error with these, it's a good idea to manually delete your
|
85
|
+
generated images folder more often as each change will build a new set of images without removing
|
86
|
+
the old ones.
|
87
|
+
|
88
|
+
- **Alternate images**
|
89
|
+
|
90
|
+
_Format:_ `(media query preset): (filename) (...)`
|
91
|
+
|
92
|
+
_Example:_ `tablet: img_cropped.jpg mobile: img_cropped_more.jpg`
|
63
93
|
|
64
94
|
Optionally specify any number of alternate base images for given [screen
|
65
95
|
sizes]({{ site.baseurl }}/presets/#media-presets) (specified in `_data/picture.yml`). This is called [art
|
@@ -70,44 +100,44 @@ Given in order:
|
|
70
100
|
be provided to the browser in reverse order, and it will select the first one with an applicable
|
71
101
|
media query.
|
72
102
|
|
73
|
-
|
103
|
+
- **Attributes**
|
74
104
|
|
75
105
|
Optionally specify any number of HTML attributes, or an href target. These will be added to any
|
76
106
|
attributes you've set in a preset.
|
77
107
|
|
78
|
-
|
108
|
+
- **`--link`**
|
79
109
|
|
80
|
-
|
110
|
+
_Format:_ `--link (some url)`
|
81
111
|
|
82
|
-
|
112
|
+
_Examples_: `--link https://example.com`, `--link /blog/some_post/`
|
83
113
|
|
84
|
-
|
85
|
-
|
114
|
+
Wrap the image in an anchor tag, with the `href` attribute set to whatever value you give it.
|
115
|
+
This will override automatic source image linking, if you have enabled it.
|
86
116
|
|
87
|
-
|
88
|
-
|
117
|
+
**Note**: If you get either mangled HTML or extra {::nomarkdown} tags when using this, read
|
118
|
+
[here]({{ site.baseurl }}/notes).
|
89
119
|
|
90
|
-
|
91
|
-
|
92
|
-
*Format:* `--alt (alt text)`
|
120
|
+
- **`--alt`**
|
93
121
|
|
94
|
-
|
122
|
+
_Format:_ `--alt (alt text)`
|
123
|
+
|
124
|
+
_Example:_ `--alt Here is my alt text!`
|
95
125
|
|
96
126
|
Convenience shortcut for `--img alt="..."`
|
97
|
-
|
98
|
-
* **`--(element)`**
|
99
127
|
|
100
|
-
|
128
|
+
- **`--(element)`**
|
129
|
+
|
130
|
+
_Format:_ `--(picture|img|source|a|parent) (Standard HTML attributes)`
|
101
131
|
|
102
|
-
|
132
|
+
_Example:_ `--img class="awesome-fade-in" id="coolio" --a data-awesomeness="11"`
|
103
133
|
|
104
134
|
Apply attributes to a given HTML element. Your options are:
|
105
135
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
136
|
+
- `picture`
|
137
|
+
- `img`
|
138
|
+
- `source`
|
139
|
+
- `a` (anchor tag)
|
140
|
+
- `parent`
|
111
141
|
|
112
142
|
`--parent` will be applied to the `<picture>` if present, otherwise the `<img>`; useful when
|
113
143
|
using an `auto` output format.
|
data/jekyll_picture_tag.gemspec
CHANGED
@@ -38,9 +38,10 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_development_dependency 'solargraph'
|
39
39
|
|
40
40
|
spec.add_dependency 'addressable', '~> 2.6'
|
41
|
+
spec.add_dependency 'base32', '~> 0.3'
|
41
42
|
spec.add_dependency 'mime-types', '~> 3'
|
42
43
|
spec.add_dependency 'mini_magick', '~> 4'
|
43
|
-
spec.add_dependency 'objective_elements', '~> 1.1.
|
44
|
+
spec.add_dependency 'objective_elements', '~> 1.1.2'
|
44
45
|
|
45
46
|
spec.add_runtime_dependency 'jekyll', '< 5'
|
46
47
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'mini_magick'
|
2
|
+
require 'base32'
|
2
3
|
|
3
4
|
module PictureTag
|
4
5
|
# Represents a generated image file.
|
@@ -6,10 +7,12 @@ module PictureTag
|
|
6
7
|
attr_reader :width, :format
|
7
8
|
include MiniMagick
|
8
9
|
|
9
|
-
def initialize(source_file:, width:, format:)
|
10
|
+
def initialize(source_file:, width:, format:, crop: nil, gravity: '')
|
10
11
|
@source = source_file
|
11
12
|
@width = width
|
12
13
|
@format = process_format format
|
14
|
+
@crop = crop
|
15
|
+
@gravity = gravity
|
13
16
|
end
|
14
17
|
|
15
18
|
def exists?
|
@@ -38,6 +41,10 @@ module PictureTag
|
|
38
41
|
ImgURI.new(name).to_s
|
39
42
|
end
|
40
43
|
|
44
|
+
def cropped_source_width
|
45
|
+
image.width
|
46
|
+
end
|
47
|
+
|
41
48
|
private
|
42
49
|
|
43
50
|
# /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
|
@@ -57,7 +64,19 @@ module PictureTag
|
|
57
64
|
# /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
|
58
65
|
# ^^^^
|
59
66
|
def name_right
|
60
|
-
'.' + @format
|
67
|
+
crop_code + '.' + @format
|
68
|
+
end
|
69
|
+
|
70
|
+
# Hash the crop settings, so we can detect when they change. We use a
|
71
|
+
# base32 encoding scheme to pack more information into fewer characters,
|
72
|
+
# without dealing with various filesystem naming limitations that would crop
|
73
|
+
# up using base64 (such as NTFS being case insensitive).
|
74
|
+
def crop_code
|
75
|
+
return '' unless @crop
|
76
|
+
|
77
|
+
Base32.encode(
|
78
|
+
Digest::MD5.hexdigest(@crop + @gravity)
|
79
|
+
)[0..2]
|
61
80
|
end
|
62
81
|
|
63
82
|
# Used for the fast build option: look for a file which matches everything
|
@@ -77,15 +96,12 @@ module PictureTag
|
|
77
96
|
|
78
97
|
# Returns a list of images which are probably correct.
|
79
98
|
def dest_glob
|
80
|
-
|
81
|
-
|
99
|
+
query = File.join(
|
100
|
+
PictureTag.dest_dir,
|
101
|
+
name_left + '?' * 6 + name_right
|
102
|
+
)
|
82
103
|
|
83
|
-
|
84
|
-
@image ||= Image.open(@source.name)
|
85
|
-
end
|
86
|
-
|
87
|
-
def dest_dir
|
88
|
-
File.dirname absolute_filename
|
104
|
+
Dir.glob query
|
89
105
|
end
|
90
106
|
|
91
107
|
def generate_image
|
@@ -94,10 +110,26 @@ module PictureTag
|
|
94
110
|
write_image
|
95
111
|
end
|
96
112
|
|
113
|
+
def image
|
114
|
+
@image ||= open_image
|
115
|
+
end
|
116
|
+
|
117
|
+
def open_image
|
118
|
+
image_base = Image.open(@source.name)
|
119
|
+
image_base.combine_options do |i|
|
120
|
+
i.auto_orient
|
121
|
+
if @crop
|
122
|
+
i.gravity @gravity
|
123
|
+
i.crop @crop
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
image_base
|
128
|
+
end
|
129
|
+
|
97
130
|
def process_image
|
98
131
|
image.combine_options do |i|
|
99
132
|
i.resize "#{@width}x"
|
100
|
-
i.auto_orient
|
101
133
|
i.strip
|
102
134
|
end
|
103
135
|
|
@@ -106,16 +138,16 @@ module PictureTag
|
|
106
138
|
end
|
107
139
|
|
108
140
|
def write_image
|
109
|
-
|
141
|
+
# Make sure destination directory exists:
|
142
|
+
FileUtils.mkdir_p(dest_dir) unless Dir.exist?(dest_dir)
|
110
143
|
|
111
144
|
image.write absolute_filename
|
112
145
|
|
113
146
|
FileUtils.chmod(0o644, absolute_filename)
|
114
147
|
end
|
115
148
|
|
116
|
-
|
117
|
-
|
118
|
-
FileUtils.mkdir_p(dest_dir) unless Dir.exist?(dest_dir)
|
149
|
+
def dest_dir
|
150
|
+
File.dirname absolute_filename
|
119
151
|
end
|
120
152
|
|
121
153
|
def process_format(format)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module PictureTag
|
2
|
+
module Instructions
|
3
|
+
# This class takes in the arguments passed to the liquid tag, and splits it
|
4
|
+
# up into 'words' (correctly handling quotes and backslash escapes.)
|
5
|
+
#
|
6
|
+
# To handle quotes and backslash escaping, we have to parse the string by
|
7
|
+
# characters to break it up correctly. I'm sure there's a library to do
|
8
|
+
# this, but it's not that much code honestly. If this starts getting big,
|
9
|
+
# we'll pull in a new dependency.
|
10
|
+
#
|
11
|
+
class ArgSplitter
|
12
|
+
attr_reader :words
|
13
|
+
|
14
|
+
def initialize(raw_params)
|
15
|
+
@words = []
|
16
|
+
@word = ''
|
17
|
+
@in_quotes = false
|
18
|
+
@escaped = false
|
19
|
+
|
20
|
+
raw_params.each_char { |c| handle_char(c) }
|
21
|
+
|
22
|
+
add_word # We have to explicitly add the last one.
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def handle_char(char)
|
28
|
+
# last character was a backslash:
|
29
|
+
if @escaped
|
30
|
+
close_escape char
|
31
|
+
|
32
|
+
# char is a backslash or a quote:
|
33
|
+
elsif char.match?(/["\\]/)
|
34
|
+
handle_special char
|
35
|
+
|
36
|
+
# Character isn't whitespace, or it's inside double quotes:
|
37
|
+
elsif @in_quotes || char.match(/\S/)
|
38
|
+
@word << char
|
39
|
+
|
40
|
+
# Character is whitespace outside of double quotes:
|
41
|
+
else
|
42
|
+
add_word
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_word
|
47
|
+
return if @word.empty?
|
48
|
+
|
49
|
+
@words << @word
|
50
|
+
@word = ''
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_special(char)
|
54
|
+
if char == '\\'
|
55
|
+
@escaped = true
|
56
|
+
elsif char == '"'
|
57
|
+
@in_quotes = !@in_quotes
|
58
|
+
@word << char
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def close_escape(char)
|
63
|
+
@word << char
|
64
|
+
@escaped = false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -12,13 +12,6 @@ module PictureTag
|
|
12
12
|
@content[key]
|
13
13
|
end
|
14
14
|
|
15
|
-
# Returns the set of widths to use for a given media query.
|
16
|
-
def widths(media)
|
17
|
-
width_hash = self['media_widths'] || {}
|
18
|
-
width_hash.default = self['widths']
|
19
|
-
width_hash[media]
|
20
|
-
end
|
21
|
-
|
22
15
|
def formats
|
23
16
|
@content['formats']
|
24
17
|
end
|
@@ -41,15 +34,38 @@ module PictureTag
|
|
41
34
|
end
|
42
35
|
end
|
43
36
|
|
37
|
+
# Image widths to generate for a given media query.
|
38
|
+
def widths(media = nil)
|
39
|
+
setting_lookup('widths', 'media', media)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Image quality setting, possibly dependent on format.
|
44
43
|
def quality(format = nil)
|
45
|
-
|
46
|
-
|
44
|
+
setting_lookup('quality', 'format', format)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Gravity setting (for imagemagick cropping)
|
48
|
+
def gravity(media = nil)
|
49
|
+
setting_lookup('gravity', 'media', media)
|
50
|
+
end
|
47
51
|
|
48
|
-
|
52
|
+
# Crop value
|
53
|
+
def crop(media = nil)
|
54
|
+
setting_lookup('crop', 'media', media)
|
49
55
|
end
|
50
56
|
|
51
57
|
private
|
52
58
|
|
59
|
+
# Return arbitrary setting values, taking their defaults into account.
|
60
|
+
# Ex: quality can be set for all image formats, or individually per
|
61
|
+
# format. Per-format settings should override the general setting.
|
62
|
+
def setting_lookup(setting, prefix, lookup)
|
63
|
+
media_values = @content[prefix + '_' + setting] || {}
|
64
|
+
media_values.default = @content[setting]
|
65
|
+
|
66
|
+
media_values[lookup]
|
67
|
+
end
|
68
|
+
|
53
69
|
def build_preset
|
54
70
|
# The _data/picture.yml file is optional.
|
55
71
|
picture_data_file = grab_data_file
|
@@ -70,10 +86,14 @@ module PictureTag
|
|
70
86
|
end
|
71
87
|
|
72
88
|
def no_preset
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
89
|
+
unless @name == 'default'
|
90
|
+
Utils.warning(
|
91
|
+
<<~HEREDOC
|
92
|
+
Preset "#{@name}" not found in {PictureTag.config['data_dir']}/picture.yml
|
93
|
+
under markup_presets key. Using default values."
|
94
|
+
HEREDOC
|
95
|
+
)
|
96
|
+
end
|
77
97
|
|
78
98
|
{}
|
79
99
|
end
|