jekyll_picture_tag 1.2.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +24 -0
  6. data/Rakefile +1 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +7 -0
  9. data/examples/_config.yml +4 -0
  10. data/examples/_data/picture.yml +85 -0
  11. data/examples/post.md +18 -0
  12. data/jekyll_picture_tag.gemspec +39 -0
  13. data/lib/jekyll_picture_tag.rb +67 -0
  14. data/lib/jekyll_picture_tag/defaults/global.yml +10 -0
  15. data/lib/jekyll_picture_tag/defaults/presets.yml +7 -0
  16. data/lib/jekyll_picture_tag/generated_image.rb +66 -0
  17. data/lib/jekyll_picture_tag/instructions.rb +105 -0
  18. data/lib/jekyll_picture_tag/instructions/configuration.rb +82 -0
  19. data/lib/jekyll_picture_tag/instructions/html_attributes.rb +59 -0
  20. data/lib/jekyll_picture_tag/instructions/preset.rb +61 -0
  21. data/lib/jekyll_picture_tag/instructions/tag_parser.rb +48 -0
  22. data/lib/jekyll_picture_tag/output_formats.rb +9 -0
  23. data/lib/jekyll_picture_tag/output_formats/auto.rb +15 -0
  24. data/lib/jekyll_picture_tag/output_formats/basics.rb +105 -0
  25. data/lib/jekyll_picture_tag/output_formats/data_attributes.rb +44 -0
  26. data/lib/jekyll_picture_tag/output_formats/data_auto.rb +14 -0
  27. data/lib/jekyll_picture_tag/output_formats/data_img.rb +8 -0
  28. data/lib/jekyll_picture_tag/output_formats/data_picture.rb +8 -0
  29. data/lib/jekyll_picture_tag/output_formats/direct_url.rb +13 -0
  30. data/lib/jekyll_picture_tag/output_formats/img.rb +24 -0
  31. data/lib/jekyll_picture_tag/output_formats/picture.rb +66 -0
  32. data/lib/jekyll_picture_tag/source_image.rb +62 -0
  33. data/lib/jekyll_picture_tag/srcsets.rb +3 -0
  34. data/lib/jekyll_picture_tag/srcsets/basics.rb +88 -0
  35. data/lib/jekyll_picture_tag/srcsets/pixel_ratio.rb +32 -0
  36. data/lib/jekyll_picture_tag/srcsets/width.rb +45 -0
  37. data/lib/jekyll_picture_tag/utils.rb +67 -0
  38. data/lib/jekyll_picture_tag/version.rb +3 -0
  39. data/migration.md +178 -0
  40. data/readme.md +787 -0
  41. metadata +191 -0
@@ -0,0 +1,8 @@
1
+ module PictureTag
2
+ module OutputFormats
3
+ # img with data-src and such instead of src
4
+ class DataImg < Img
5
+ include DataAttributes
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module PictureTag
2
+ module OutputFormats
3
+ # picture tag with data-srcsets and such instead of src.
4
+ class DataPicture < Picture
5
+ include DataAttributes
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module PictureTag
2
+ module OutputFormats
3
+ # Represents a bare url you can use in another context, such as a direct
4
+ # link, but keep the resizing functionality
5
+ class DirectUrl
6
+ include PictureTag::OutputFormats::Basics
7
+
8
+ def to_s
9
+ build_base_img.src
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module PictureTag
2
+ module OutputFormats
3
+ # Represents a bare <img> tag with a srcset attribute.
4
+ # Used when <picture> is unnecessary.
5
+ class Img
6
+ include PictureTag::OutputFormats::Basics
7
+
8
+ def srcset
9
+ build_srcset(nil, PictureTag.preset['formats'].first)
10
+ end
11
+
12
+ def base_markup
13
+ img = build_base_img
14
+
15
+ add_srcset(img, srcset)
16
+ add_sizes(img, srcset)
17
+
18
+ img.attributes << PictureTag.html_attributes['parent']
19
+
20
+ img
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,66 @@
1
+ module PictureTag
2
+ module OutputFormats
3
+ # Represents a <picture> tag, enclosing at least 2 <source> tags and an
4
+ # <img> tag.
5
+ class Picture
6
+ include Basics
7
+
8
+ def srcsets
9
+ sets = []
10
+
11
+ PictureTag.preset['formats'].each do |format|
12
+ # We have 2 dimensions here: formats, and source images. Formats are
13
+ # provided in the order they must be returned, source images are
14
+ # provided in the reverse (least to most preferable) and must be
15
+ # flipped. We'll use an intermediate value to accomplish this.
16
+ format_set = []
17
+
18
+ # Source images are defined in the tag params, and associated with
19
+ # media queries. The base (first provided) image has a key of nil.
20
+ PictureTag.source_images.each_key do |media|
21
+ format_set << build_srcset(media, format)
22
+ end
23
+ sets.concat format_set.reverse
24
+ end
25
+
26
+ sets
27
+ end
28
+
29
+ def build_sources
30
+ srcsets.collect { |s| build_source(s) }
31
+ end
32
+
33
+ def build_source(srcset)
34
+ source = SingleTag.new(
35
+ 'source',
36
+ attributes: PictureTag.html_attributes['source']
37
+ )
38
+
39
+ # Sizes will be the same for all sources. There's some redundant markup
40
+ # here, but I don't think it's worth the effort to prevent.
41
+ add_sizes(source, srcset)
42
+ add_media(source, srcset)
43
+ add_srcset(source, srcset)
44
+
45
+ source.type = srcset.mime_type
46
+
47
+ source
48
+ end
49
+
50
+ def base_markup
51
+ picture = DoubleTag.new(
52
+ 'picture',
53
+ attributes: PictureTag.html_attributes['picture'],
54
+ content: build_sources << build_base_img,
55
+
56
+ # Markdown fix requires removal of line breaks:
57
+ oneline: PictureTag.nomarkdown?
58
+ )
59
+
60
+ picture.attributes << PictureTag.html_attributes['parent']
61
+
62
+ picture
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
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 source images.
5
+ class SourceImage
6
+ attr_reader :name, :shortname
7
+
8
+ def initialize(relative_filename)
9
+ @shortname = relative_filename
10
+ @name = grab_file relative_filename
11
+ end
12
+
13
+ def size
14
+ @size ||= build_size
15
+ end
16
+
17
+ def width
18
+ size[:width]
19
+ end
20
+
21
+ def aspect_ratio
22
+ @aspect_ratio ||= size[:width].to_f / size[:height].to_f
23
+ end
24
+
25
+ def digest
26
+ @digest ||= Digest::MD5.hexdigest(File.read(@name))[0..5]
27
+ end
28
+
29
+ # Includes path relative to default sorce folder, and the original filename.
30
+ def base_name
31
+ @shortname.delete_suffix File.extname(@shortname)
32
+ end
33
+
34
+ # File exention
35
+ def ext
36
+ # [1..-1] will strip the leading period.
37
+ @ext ||= File.extname(@name)[1..-1].downcase
38
+ end
39
+
40
+ private
41
+
42
+ def build_size
43
+ width, height = FastImage.size(@name)
44
+
45
+ {
46
+ width: width,
47
+ height: height
48
+ }
49
+ end
50
+
51
+ # Turn a relative filename into an absolute one, and make sure it exists.
52
+ def grab_file(source_file)
53
+ source_name = File.join(PictureTag.config.source_dir, source_file)
54
+
55
+ unless File.exist? source_name
56
+ raise "Jekyll Picture Tag could not find #{source_name}."
57
+ end
58
+
59
+ source_name
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'srcsets/basics'
2
+ require_relative 'srcsets/pixel_ratio'
3
+ require_relative 'srcsets/width'
@@ -0,0 +1,88 @@
1
+ module PictureTag
2
+ # Handles srcset generation, which also handles file generation.
3
+ module Srcsets
4
+ # Basic functionality for a srcset, which also handles file generation.
5
+ # Classes including this module must implement the to_a method, which
6
+ # accomplishes the following:
7
+ # - Return an array of srcset entries.
8
+ # - Call generate_file for each entry, giving it the desired width in
9
+ # pixels.
10
+ module Basics
11
+ require 'fastimage'
12
+ attr_reader :media, :source_image
13
+
14
+ def initialize(media:, format:)
15
+ @media = media # Associated Media Query, can be nil
16
+
17
+ # Output format:
18
+ @format = Utils.process_format(format, media)
19
+
20
+ @source_image = PictureTag.source_images[@media]
21
+ end
22
+
23
+ def to_s
24
+ to_a.join(', ')
25
+ end
26
+
27
+ # Allows us to add a type attribute to whichever element contains this
28
+ # srcset.
29
+ def mime_type
30
+ mime_types[@format]
31
+ end
32
+
33
+ # Some srcsets have them, for those that don't return nil.
34
+ def sizes
35
+ nil
36
+ end
37
+
38
+ # Check our source image size vs requested sizes
39
+ def check_widths(targets)
40
+ if targets.any? { |t| t > @source_image.width }
41
+ handle_small_source(targets, @source_image.width)
42
+ else
43
+ targets
44
+ end
45
+ end
46
+
47
+ # Generates an HTML attribute
48
+ def media_attribute
49
+ "(#{PictureTag.media_presets[@media]})"
50
+ end
51
+
52
+ private
53
+
54
+ def handle_small_source(targets, image_width)
55
+ PictureTag::Utils.warning(
56
+ " #{@source_image.shortname} is #{image_width}px wide, smaller than" \
57
+ " at least one size in the set #{targets}. Will not enlarge."
58
+ )
59
+
60
+ small_targets = targets.dup.delete_if { |t| t >= image_width }
61
+
62
+ small_targets.push image_width
63
+
64
+ small_targets
65
+ end
66
+
67
+ def generate_file(width)
68
+ GeneratedImage.new(
69
+ source_file: @source_image,
70
+ width: width,
71
+ format: @format
72
+ )
73
+ end
74
+
75
+ # Hardcoding these isn't ideal, but I'm not pulling in a new dependency
76
+ # for 9 lines of easy code.
77
+ def mime_types
78
+ {
79
+ 'gif' => 'image/gif',
80
+ 'jpg' => 'image/jpeg',
81
+ 'jpeg' => 'image/jpeg',
82
+ 'png' => 'image/png',
83
+ 'webp' => 'image/webp'
84
+ }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,32 @@
1
+ module PictureTag
2
+ module Srcsets
3
+ # Creates a srcset in the "(filename) (pixel_ratio)x" format.
4
+ # Example: "img.jpg 1x, img2.jpg 1.5x, img3.jpg 2x"
5
+ class PixelRatio
6
+ include Basics
7
+
8
+ def to_a
9
+ widths.collect { |w| build_srcset_entry(w) }
10
+ end
11
+
12
+ private
13
+
14
+ def widths
15
+ target = PictureTag.preset['pixel_ratios'].collect do |p|
16
+ p * PictureTag.preset['base_width']
17
+ end
18
+
19
+ check_widths target
20
+ end
21
+
22
+ def build_srcset_entry(width)
23
+ # We have to recalculate the pixel ratio after verifying our source
24
+ # image is large enough.
25
+ pixel_ratio = (width.to_f / PictureTag.preset['base_width']).round(2)
26
+ file = generate_file(width)
27
+
28
+ "#{PictureTag.build_url(file.name)} #{pixel_ratio}x"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ module PictureTag
2
+ module Srcsets
3
+ # Creates a srcset in the "(filename) (width)w, (...)" format.
4
+ # Example: "img.jpg 400w, img2.jpg 600w, img3.jpg 800w"
5
+ class Width
6
+ include Basics
7
+
8
+ def to_a
9
+ widths.collect { |w| build_srcset_entry(w) }
10
+ end
11
+
12
+ # Sizes html attribute. Since it's intimately related to srcset, we
13
+ # generate it at the same time.
14
+ def sizes
15
+ preset_sizes = PictureTag.preset['sizes'] || {}
16
+ preset_size = PictureTag.preset['size']
17
+ size_set = []
18
+
19
+ preset_sizes.each_pair do |media, size|
20
+ size_set << build_size_entry(media, size)
21
+ end
22
+
23
+ size_set << preset_size if preset_size
24
+
25
+ size_set.any? ? size_set.join(', ') : nil
26
+ end
27
+
28
+ private
29
+
30
+ def widths
31
+ check_widths PictureTag.widths(@media)
32
+ end
33
+
34
+ def build_srcset_entry(width)
35
+ file = generate_file(width)
36
+
37
+ "#{PictureTag.build_url(file.name)} #{file.width}w"
38
+ end
39
+
40
+ def build_size_entry(media, size)
41
+ "(#{PictureTag.media_presets[media]}) #{size}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,67 @@
1
+ module PictureTag
2
+ # This is a little module to hold logic that doesn't fit other places. If it
3
+ # starts getting big, refactor.
4
+ module Utils
5
+ # Configure Jekyll to keep our generated files
6
+ def self.keep_files
7
+ dest_dir = PictureTag.config['picture']['output']
8
+
9
+ # Chop a slash off the end, if it's there. Doesn't work otherwise.
10
+ dest_dir = dest_dir[0..-2] if dest_dir =~ %r{/\z}
11
+
12
+ return if PictureTag.site.config['keep_files'].include?(dest_dir)
13
+
14
+ PictureTag.site.config['keep_files'] << dest_dir
15
+ end
16
+
17
+ # Print a warning to the console
18
+ def self.warning(message)
19
+ return if PictureTag.config['picture']['suppress_warnings']
20
+
21
+ warn 'Jekyll Picture Tag Warning: '.yellow + message
22
+ end
23
+
24
+ # Parse a liquid template; allows liquid variables to be included as tag
25
+ # params.
26
+ def self.liquid_lookup(params)
27
+ Liquid::Template.parse(params).render(PictureTag.context)
28
+
29
+ # This gsub allows people to include template code for javascript
30
+ # libraries such as handlebar.js. It adds complication and I'm not sure
31
+ # it has much value now, so I'm commenting it out. If someone has a use
32
+ # case for it we can add it back in.
33
+ # .gsub(/\\\{\\\{|\\\{\\%/, '\{\{' => '{{', '\{\%' => '{%')
34
+ end
35
+
36
+ # Allows us to use 'original' as a format name.
37
+ def self.process_format(format, media)
38
+ if format.casecmp('original').zero?
39
+ PictureTag.source_images[media].ext
40
+ else
41
+ format.downcase
42
+ end
43
+ end
44
+
45
+ # Used for auto markup configuration and such
46
+ def self.count_srcsets
47
+ formats = PictureTag.preset['formats'].length
48
+ source_images = PictureTag.source_images.length
49
+
50
+ formats * source_images
51
+ end
52
+
53
+ # Returns whether or not the current page is a markdown file.
54
+ def self.markdown_page?
55
+ page_name = PictureTag.page['name']
56
+ page_ext = PictureTag.page['ext']
57
+ ext = page_ext ? page_ext : File.extname(page_name)
58
+
59
+ ext.casecmp('.md').zero? || ext.casecmp('.markdown').zero?
60
+ end
61
+
62
+ # Returns the widest source image
63
+ def self.biggest_source
64
+ PictureTag.source_images.values.max_by(&:width)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module PictureTag
2
+ VERSION = '1.2.0'.freeze
3
+ end
@@ -0,0 +1,178 @@
1
+ # Update and Migration guide
2
+
3
+ This document details the changes from previous versions (Everything before 1.0), and how to migrate
4
+ an existing site to the new version. For now it's a markdown document, once this branch is merged
5
+ into master it'll be a wiki page.
6
+
7
+ ## Changes from previous versions:
8
+
9
+ - The default output formats are bone-stock HTML. Picturefill, as of version 3, no longer requires
10
+ special markup, so it remains compatible. Other javascript image solutions are supported with
11
+ the `data_` selection of markup formats. Interchange support is removed, though adding it again
12
+ is not difficult if there is demand for it.
13
+ - There are now 2 basic markup formats to choose from: `<source>` tags within a `<picture>`, and a
14
+ single `<img>` tag. If markup is set to 'auto', or if it is not set at all, the plugin will
15
+ automatically determine which is most appropriate. These formats also have `data_` counterparts
16
+ (i.e. `data_auto`), which accomplish the same thing except setting respective data-attributes
17
+ to allow for lazy loading and such.
18
+ - There are also 2 srcset formats: one which supplies image width, and another which supplies
19
+ multiples (pixel ratios) of the base size.
20
+ - Source Keys are replaced by media query presets, which can also be used to create the 'sizes'
21
+ attribute.
22
+ - Jekyll Picture Tag no longer accepts height arguments, and will no longer crop images for you.
23
+ Aspect ratio will always be maintained.
24
+ - Multiple image widths are now supported, which will be used to create a corresponding srcset.
25
+ - Multiple image formats are now possible, including webp.
26
+ - PictureTag can now generate sizes attributes.
27
+ - Configuration takes a very different format. It should be simpler to write your config than the
28
+ old version, and migrating to it should not be difficult. Instead of creating individual source
29
+ keys, you supply an array of widths you'd like to construct. You can also supply an array (yaml
30
+ sequence) of formats, including 'original'.
31
+ - Only global settings are placed in `_config.yml`. Presets are moved to `_data/picture.yml`.
32
+
33
+ ## Migration
34
+
35
+ ### Liquid Tags
36
+
37
+ Previous tag syntax has been extended, but backwards compatibility (and behaviour of previous
38
+ versions) is maintained.
39
+
40
+ `{% picture img.jpg (implicit attributes) --(argument) (explicit attributes) %}`
41
+
42
+ Implicit attributes are the old way. They are specified after the last source image, and before any
43
+ explicit attributes (if they are included). These attributes are applied to the `<img>` tag, as in
44
+ previous versions.
45
+
46
+ Explicit attributes are the new way, documented in the readme. It is possible to use a mix of both,
47
+ though I'm not sure why you would want to.
48
+
49
+ There is one instance I can think of where you will have to change your tags:
50
+
51
+ - You use art direction with more than one different preset.
52
+ - These presets have source keys of the same name.
53
+ - These source keys have different associated media queries.
54
+
55
+ If all of the above are true, you will either have to pick one media query which works for both, or
56
+ rename one of them and change it everywhere it's used.
57
+
58
+ Another trouble spot will be CSS; your output markup may change, meaning your CSS selectors may stop
59
+ working.
60
+
61
+ Outside of those situations, and provided you have created media_presets to match your old source
62
+ keys, your existing tags should keep working. If they don't, it's a bug. Please report it.
63
+
64
+ ### Configuration
65
+
66
+ The new configuration is described in the readme so I won't document it here, but I will show an old
67
+ config alongside an equivalent new one.
68
+
69
+ Example old configuration:
70
+
71
+ ```yml
72
+ # _config.yml
73
+
74
+ picture:
75
+ source: assets/images/_fullsize
76
+ output: generated
77
+ markup: picture
78
+ presets:
79
+ # Full width pictures
80
+ default:
81
+ ppi: [1, 1.5]
82
+ attr:
83
+ class: blog-full
84
+ itemprop: image
85
+ source_lrg:
86
+ media: "(min-width: 40em)"
87
+ width: 700
88
+ source_med:
89
+ media: "(min-width: 30em)"
90
+ width: 450
91
+ source_default:
92
+ width: 350
93
+ height: 200
94
+ # Half width pictures
95
+ half:
96
+ ppi: [1, 1.5]
97
+ attr:
98
+ data-location: "{{location}}"
99
+ data-active: nil
100
+ source_lrg:
101
+ media: "(min-width: 40em)"
102
+ width: 400
103
+ source_med:
104
+ media: "(min-width: 30em)"
105
+ width: 250
106
+ source_default:
107
+ width: 350
108
+ # Self-set resolution sources. Useful if you don't want a 1:1 image size to dppx ratio.
109
+ gallery:
110
+ source_wide_hi:
111
+ media: "(min-width: 40em) and (min-resolution: 1.5dppx)"
112
+ width: 900
113
+ height: 600
114
+ source_wide:
115
+ media: "(min-width: 40em)"
116
+ width: 600
117
+ height: 400
118
+ source_default:
119
+ width: 250
120
+ height: 250
121
+ ```
122
+
123
+ Equivalent new configuration:
124
+
125
+ ```yml
126
+ # _config.yml
127
+
128
+ picture:
129
+ source: assets/images/_fullsize
130
+ output: generated
131
+ ```
132
+
133
+ ```yml
134
+ # _data/picture.yml
135
+
136
+ # Media presets are named media queries. To maintain compatibility with your tags, you need to
137
+ # create presets of the same name as your old source keys. There is no limit to how many of them you
138
+ # can have, so you're free to create additional new ones with better names to use going forward.
139
+ media_presets:
140
+ source_lrg: '(min-width: 40em)'
141
+ source_med: '(min-width: 30em)'
142
+ source_wide_hi: "(min-width: 40em) and (min-resolution: 1.5dppx)"
143
+ source_wide: "(min-width: 40em)"
144
+
145
+ markup_presets:
146
+ # You can't specify both widths and pixel ratios anymore. Choose one.
147
+ # Full width pictures, width-based srcset
148
+ default:
149
+ markup: picture
150
+ widths: [350, 450, 700]
151
+ attributes:
152
+ picture: 'class="blog-full" itemprop="image"'
153
+
154
+ # Full width pictures, multiplier based srcset
155
+ default-ppi:
156
+ markup: picture
157
+ base_width: 350
158
+ pixel_ratios: [1, 1.5]
159
+ attributes:
160
+ picture: 'class="blog-full" itemprop="image"'
161
+
162
+ # Half width pictures
163
+ half:
164
+ widths: [250, 350, 400]
165
+ attributes:
166
+ picture: 'data-location="{{location}}" data-active="nil"'
167
+
168
+ # Note you can't set heights anymore. You'll have to crop your images either ahead of time, or
169
+ # do it with CSS.
170
+ #
171
+ # You have to use arrays for widths, even if only specifying a single value. There's no reason you
172
+ # can't add more.
173
+ gallery:
174
+ widths: [250]
175
+ media_widths:
176
+ source_wide_hi: [900]
177
+ source_wide: [600]
178
+ ```