jekyll_picture_tag 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ ```