jekyll_picture_tag 1.7.1 → 1.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -7
  3. data/Dockerfile +9 -0
  4. data/docs/Gemfile.lock +183 -88
  5. data/docs/contributing.md +50 -16
  6. data/docs/example_presets.md +1 -1
  7. data/docs/global_configuration.md +55 -2
  8. data/docs/index.md +26 -20
  9. data/docs/installation.md +22 -7
  10. data/docs/notes.md +11 -1
  11. data/docs/output.md +32 -21
  12. data/docs/presets.md +109 -54
  13. data/docs/releases.md +17 -1
  14. data/docs/usage.md +68 -38
  15. data/jekyll_picture_tag.gemspec +2 -1
  16. data/lib/jekyll_picture_tag.rb +27 -10
  17. data/lib/jekyll_picture_tag/defaults/global.yml +2 -0
  18. data/lib/jekyll_picture_tag/defaults/presets.yml +2 -0
  19. data/lib/jekyll_picture_tag/generated_image.rb +105 -19
  20. data/lib/jekyll_picture_tag/instructions.rb +1 -0
  21. data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +68 -0
  22. data/lib/jekyll_picture_tag/instructions/configuration.rb +47 -11
  23. data/lib/jekyll_picture_tag/instructions/preset.rb +34 -14
  24. data/lib/jekyll_picture_tag/instructions/set.rb +18 -8
  25. data/lib/jekyll_picture_tag/instructions/tag_parser.rb +59 -69
  26. data/lib/jekyll_picture_tag/output_formats/basic.rb +36 -7
  27. data/lib/jekyll_picture_tag/output_formats/data_attributes.rb +4 -1
  28. data/lib/jekyll_picture_tag/router.rb +16 -0
  29. data/lib/jekyll_picture_tag/source_image.rb +6 -1
  30. data/lib/jekyll_picture_tag/srcsets/basic.rb +45 -19
  31. data/lib/jekyll_picture_tag/srcsets/pixel_ratio.rb +1 -3
  32. data/lib/jekyll_picture_tag/srcsets/width.rb +1 -1
  33. data/lib/jekyll_picture_tag/utils.rb +18 -0
  34. data/lib/jekyll_picture_tag/version.rb +1 -1
  35. data/readme.md +39 -16
  36. metadata +24 -8
@@ -1,7 +1,23 @@
1
1
  ---
2
2
  ---
3
3
  # Release History
4
-
4
+ * 1.10.2 July 6, 2020
5
+ * Bugfix for fallback image files not actually getting generated
6
+ * 1.10.1 July 2, 2020
7
+ * Bugfix for erroneously regenerated images
8
+ * 1.10.0 May 11, 2020
9
+ * **Image Cropping support!** access the power of ImageMagick's `crop` function.
10
+ * Don't issue a warning when `default` preset is not found.
11
+ * Documentation improvements
12
+ * 1.9.0 Feb 2, 2020
13
+ * Add `fast_build` global setting
14
+ * Add `disabled` global setting
15
+ * Reduce unnecessary disk IO; sites with many source images should see build times improve when
16
+ no new images need to be generated.
17
+ * Add support for empty attributes; specifically so best-practice for decorative images (`alt=""`)
18
+ is possible.
19
+ * 1.8.0 Nov 25, 2019
20
+ * Add `data_sizes` setting for the `data_` family of output formats.
5
21
  * 1.7.1 Nov 14, 2019
6
22
  * Fix some HTML attribute related bugs
7
23
  * Add a few items to the FAQ
@@ -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] (base image) [alternate images] [attributes] %}`
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
- * **Preset**
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
- * **Base Image** (Required)
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
- image.jpg`).
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
- * **Alternate images**
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
- *Format:* `(media query preset): (filename) (...)`
81
+ For detailed documentation, see ImageMagick's
82
+ [crop](http://www.imagemagick.org/script/command-line-options.php#crop) tool.
61
83
 
62
- *Example:* `tablet: img_cropped.jpg mobile: img_cropped_more.jpg`
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
- * **Attributes**
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
- * **`--link`**
108
+ - **`--link`**
79
109
 
80
- *Format:* `--link (some url)`
110
+ _Format:_ `--link (some url)`
81
111
 
82
- *Examples*: `--link https://example.com`, `--link /blog/some_post/`
112
+ _Examples_: `--link https://example.com`, `--link /blog/some_post/`
83
113
 
84
- Wrap the image in an anchor tag, with the `href` attribute set to whatever value you give it.
85
- This will override automatic source image linking, if you have enabled it.
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
- **Note**: If you get either mangled HTML or extra {::nomarkdown} tags when using this, read
88
- [here]({{ site.baseurl }}/notes).
117
+ **Note**: If you get either mangled HTML or extra {::nomarkdown} tags when using this, read
118
+ [here]({{ site.baseurl }}/notes).
89
119
 
90
- * **`--alt`**
91
-
92
- *Format:* `--alt (alt text)`
120
+ - **`--alt`**
93
121
 
94
- *Example:* `--alt Here is my alt text!`
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
- *Format:* `--(picture|img|source|a|parent) (Standard HTML attributes)`
128
+ - **`--(element)`**
129
+
130
+ _Format:_ `--(picture|img|source|a|parent) (Standard HTML attributes)`
101
131
 
102
- *Example:* `--img class="awesome-fade-in" id="coolio" --a data-awesomeness="11"`
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
- * `picture`
107
- * `img`
108
- * `source`
109
- * `a` (anchor tag)
110
- * `parent`
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.
@@ -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.1'
44
+ spec.add_dependency 'objective_elements', '~> 1.1.2'
44
45
 
45
46
  spec.add_runtime_dependency 'jekyll', '< 5'
46
47
  end
@@ -18,9 +18,9 @@ require_relative 'jekyll_picture_tag/router'
18
18
  #
19
19
  # Description: Easy responsive images for Jekyll.
20
20
  #
21
- # Download: https://github.com/rbuchberger/jekyll_picture_tag
22
- # Documentation: https://github.com/rbuchberger/jekyll_picture_tag/readme.md
23
- # Issues: https://github.com/rbuchberger/jekyll_picture_tag/issues
21
+ # Download: https://rubygems.org/gems/jekyll_picture_tag
22
+ # Documentation: https://rbuchberger.github.io/jekyll_picture_tag/
23
+ # Issues: https://github.com/rbuchberger/jekyll_picture_tag/
24
24
  #
25
25
  # Syntax:
26
26
  # {% picture [preset] img.jpg [media_query: alt-img.jpg] [attributes] %}
@@ -39,30 +39,47 @@ require_relative 'jekyll_picture_tag/router'
39
39
  #
40
40
  # See the documentation for full configuration and usage instructions.
41
41
  module PictureTag
42
+ # The router module is important. If you're looking for the actual code which
43
+ # handles a `PictureTag.(some method)`, start there.
42
44
  extend Router
45
+
43
46
  ROOT_PATH = __dir__
44
47
 
45
48
  # This is the actual liquid tag, which provides the interface with Jekyll.
46
49
  class Picture < Liquid::Tag
50
+ # First jekyll initializes our class with a few arguments, of which we only
51
+ # care about the params (arguments passed to the liquid tag). Jekyll makes
52
+ # no attempt to parse them; they're given as a string.
47
53
  def initialize(tag_name, raw_params, tokens)
48
54
  @raw_params = raw_params
49
55
  super
50
56
  end
51
57
 
58
+ # Then jekyll calls the 'render' method and passes it a mostly undocumented
59
+ # context object, which appears to hold the entire site including its
60
+ # configuration and the parsed _data dir.
52
61
  def render(context)
53
- # Jekyll passes in a mostly undocumented context object, which appears to
54
- # hold the entire site, including configuration and the _data dir.
62
+ setup(context)
63
+
64
+ if PictureTag.disabled?
65
+ ''
66
+ else
67
+ PictureTag.output_class.new.to_s
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def setup(context)
55
74
  PictureTag.context = context
56
75
 
57
- # The instruction set depends on both the context and the tag parameters:
76
+ # Now that we have both the tag parameters and the context object, we can
77
+ # build our instruction set.
58
78
  PictureTag.instructions = Instructions::Set.new(@raw_params)
59
79
 
60
80
  # We need to explicitly prevent jekyll from overwriting our generated
61
- # files:
81
+ # image files:
62
82
  Utils.keep_files
63
-
64
- # Return a string:
65
- PictureTag.output_class.new.to_s
66
83
  end
67
84
  end
68
85
  end
@@ -7,3 +7,5 @@ picture:
7
7
  cdn_environments: ['production']
8
8
  nomarkdown: true
9
9
  ignore_missing_images: false
10
+ disabled: false
11
+ fast_build: false
@@ -6,3 +6,5 @@ fallback_format: original
6
6
  noscript: false
7
7
  link_source: false
8
8
  quality: 75
9
+ data_sizes: true
10
+ gravity: center
@@ -1,42 +1,135 @@
1
1
  require 'mini_magick'
2
+ require 'base32'
2
3
 
3
4
  module PictureTag
4
- # Generated Image
5
- # Represents a generated source file.
5
+ # Represents a generated image file.
6
6
  class GeneratedImage
7
7
  attr_reader :width, :format
8
8
  include MiniMagick
9
9
 
10
- def initialize(source_file:, width:, format:)
10
+ def initialize(source_file:, width:, format:, crop: nil, gravity: '')
11
11
  @source = source_file
12
12
  @width = width
13
13
  @format = process_format format
14
+ @crop = crop
15
+ @gravity = gravity
16
+ end
14
17
 
15
- generate_image unless File.exist?(absolute_filename) || @source.missing
18
+ def exists?
19
+ File.exist?(absolute_filename)
16
20
  end
17
21
 
18
- def name
19
- "#{@source.base_name}-#{@width}-#{@source.digest}.#{@format}"
22
+ def generate
23
+ generate_image unless @source.missing || exists?
20
24
  end
21
25
 
26
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
27
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22
28
  def absolute_filename
23
29
  @absolute_filename ||= File.join(PictureTag.dest_dir, name)
24
30
  end
25
31
 
32
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
33
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34
+ def name
35
+ name_left + digest + name_right
36
+ end
37
+
38
+ # https://example.com/assets/images/myimage-100-123abc.jpg
39
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26
40
  def uri
27
41
  ImgURI.new(name).to_s
28
42
  end
29
43
 
44
+ def cropped_source_width
45
+ image.width
46
+ end
47
+
30
48
  private
31
49
 
50
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
51
+ # ^^^^^^^^^^^^^^^^^^^^^^^
52
+ def name_left
53
+ "#{@source.base_name}-#{@width}-"
54
+ end
55
+
56
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
57
+ # ^^^^^^
58
+ def digest
59
+ guess_digest if !@source.digest_guess && PictureTag.fast_build?
60
+
61
+ @source.digest
62
+ end
63
+
64
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
65
+ # ^^^^
66
+ def name_right
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]
80
+ end
81
+
82
+ # Used for the fast build option: look for a file which matches everything
83
+ # we know about the destination file without calculating a digest on the
84
+ # source file, and if it exists we assume it's the right one.
85
+ def guess_digest
86
+ matches = dest_glob
87
+ return unless matches.length == 1
88
+
89
+ # Start and finish of the destination image's hash value
90
+ finish = -name_right.length
91
+ start = finish - 6
92
+
93
+ # The source image keeps track of this guess, so we hand it off:
94
+ @source.digest_guess = matches.first[start...finish]
95
+ end
96
+
97
+ # Returns a list of images which are probably correct.
98
+ def dest_glob
99
+ query = File.join(
100
+ PictureTag.dest_dir,
101
+ name_left + '?' * 6 + name_right
102
+ )
103
+
104
+ Dir.glob query
105
+ end
106
+
107
+ def generate_image
108
+ puts 'Generating new image file: ' + name
109
+ process_image
110
+ write_image
111
+ end
112
+
32
113
  def image
33
- @image ||= Image.open(@source.name)
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
34
128
  end
35
129
 
36
130
  def process_image
37
131
  image.combine_options do |i|
38
132
  i.resize "#{@width}x"
39
- i.auto_orient
40
133
  i.strip
41
134
  end
42
135
 
@@ -44,24 +137,17 @@ module PictureTag
44
137
  image.quality PictureTag.quality(@format)
45
138
  end
46
139
 
47
- def generate_image
48
- puts 'Generating new image file: ' + name
49
- process_image
50
- write_image
51
- end
52
-
53
140
  def write_image
54
- check_dest_dir
141
+ # Make sure destination directory exists:
142
+ FileUtils.mkdir_p(dest_dir) unless Dir.exist?(dest_dir)
55
143
 
56
144
  image.write absolute_filename
57
145
 
58
146
  FileUtils.chmod(0o644, absolute_filename)
59
147
  end
60
148
 
61
- # Make sure destination directory exists
62
- def check_dest_dir
63
- dir = File.dirname absolute_filename
64
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
149
+ def dest_dir
150
+ File.dirname absolute_filename
65
151
  end
66
152
 
67
153
  def process_format(format)