jekyll-og-image 1.0.3 → 1.0.5
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.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/examples/1.png +0 -0
- data/examples/2.png +0 -0
- data/lib/jekyll_og_image/config.rb +3 -0
- data/lib/jekyll_og_image/element/base.rb +24 -0
- data/lib/jekyll_og_image/element/border.rb +31 -31
- data/lib/jekyll_og_image/element/canvas.rb +34 -0
- data/lib/jekyll_og_image/element/image.rb +50 -19
- data/lib/jekyll_og_image/element/text.rb +17 -32
- data/lib/jekyll_og_image/generator.rb +33 -21
- data/lib/jekyll_og_image/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fc4622681cf9b4953d6cbd7de3b661ebd217de44c08205656e395386b14e4c9
|
4
|
+
data.tar.gz: 26b864c6981a1b353de1e8e97de690870e2f2d9c531d56dac1bf97bc2efc0cde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7285bbd3562d0e2fa67a1f92c639d2ffa505e73d0d6b58c705217e8e7eefbb5930b9c01fec2c5d267f3448cd012e76ed5f2748b639d62bde290d796ab87d31d8
|
7
|
+
data.tar.gz: beb92471fe217bc7c105fb3924940fee58031306cf628aac36ff8a4b61841ebaee406c8a85ce6aa903563fe3ca8dd489d68d0b3018f9797e77cb76da209d08ef
|
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
A Jekyll plugin to automatically generate open graph images for posts.
|
4
4
|
|
5
|
+
[](https://badge.fury.io/rb/jekyll-og-image)
|
5
6
|
[](https://github.com/igor-alexandrov/jekyll-og-image/actions/workflows/lint.yml)
|
6
7
|
[](https://github.com/igor-alexandrov/jekyll-og-image/actions/workflows/specs.yml)
|
7
8
|
|
@@ -46,6 +47,7 @@ og_image:
|
|
46
47
|
```yaml
|
47
48
|
og_image:
|
48
49
|
output_dir: "assets/images/og"
|
50
|
+
image: "/assets/images/igor.jpeg"
|
49
51
|
domain: "igor.works"
|
50
52
|
border_bottom:
|
51
53
|
width: 20
|
data/examples/1.png
CHANGED
Binary file
|
data/examples/2.png
CHANGED
Binary file
|
@@ -5,12 +5,15 @@ require "anyway_config"
|
|
5
5
|
class JekyllOgImage::Config < Anyway::Config
|
6
6
|
attr_config output_dir: "assets/images/og"
|
7
7
|
attr_config force: false
|
8
|
+
attr_config :image
|
8
9
|
attr_config :domain
|
9
10
|
attr_config border_bottom: {
|
10
11
|
width: 20,
|
11
12
|
fill: [ "#211F1F", "#F4CBB2", "#AD5C51", "#9CDAF1", "#7DBBE6" ]
|
12
13
|
}
|
13
14
|
|
15
|
+
coerce_types image: { type: :string }
|
16
|
+
|
14
17
|
coerce_types border_bottom: {
|
15
18
|
width: { type: :integer },
|
16
19
|
fill: { type: :string, array: true }
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JekyllOgImage::Element::Base
|
4
|
+
VALID_GRAVITY = %i[nw ne sw se].freeze
|
5
|
+
|
4
6
|
private
|
5
7
|
|
6
8
|
def hex_to_rgb(input)
|
@@ -13,4 +15,26 @@ class JekyllOgImage::Element::Base
|
|
13
15
|
raise ArgumentError, "Unknown input #{input.inspect}"
|
14
16
|
end
|
15
17
|
end
|
18
|
+
|
19
|
+
def validate_gravity!
|
20
|
+
unless VALID_GRAVITY.include?(@gravity)
|
21
|
+
raise ArgumentError, "Invalid gravity: #{@gravity.inspect}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def composite_with_gravity(canvas, overlay, x, y)
|
26
|
+
if gravity_nw?
|
27
|
+
canvas.composite(overlay, :over, x: [ x ], y: [ y ]).flatten
|
28
|
+
elsif gravity_ne?
|
29
|
+
x = canvas.width - overlay.width - x
|
30
|
+
canvas.composite(overlay, :over, x: [ x ], y: [ y ]).flatten
|
31
|
+
elsif gravity_sw?
|
32
|
+
y = canvas.height - overlay.height - y
|
33
|
+
canvas.composite(overlay, :over, x: [ x ], y: [ y ]).flatten
|
34
|
+
elsif gravity_se?
|
35
|
+
x = canvas.width - overlay.width - x
|
36
|
+
y = canvas.height - overlay.height - y
|
37
|
+
canvas.composite(overlay, :over, x: [ x ], y: [ y ]).flatten
|
38
|
+
end
|
39
|
+
end
|
16
40
|
end
|
@@ -4,43 +4,57 @@ class JekyllOgImage::Element::Border < JekyllOgImage::Element::Base
|
|
4
4
|
class Part < Data.define(:rgb, :width, :height, :offset)
|
5
5
|
end
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@canvas = canvas
|
7
|
+
def initialize(size, position: :bottom, fill: "#000000")
|
9
8
|
@size = size
|
10
9
|
@position = position
|
11
10
|
@fill = fill
|
12
11
|
|
13
12
|
validate_position!
|
13
|
+
end
|
14
14
|
|
15
|
-
|
15
|
+
def apply_to(canvas, &block)
|
16
|
+
border = Vips::Image.black(*dimensions(canvas))
|
16
17
|
|
17
|
-
parts.each.with_index do |part, index|
|
18
|
-
|
18
|
+
parts(canvas).each.with_index do |part, index|
|
19
|
+
image = Vips::Image.black(part.width, part.height).ifthenelse([ 0, 0, 0 ], part.rgb)
|
19
20
|
|
20
21
|
if vertical?
|
21
|
-
|
22
|
+
border = border.composite(image, :over, x: [ 0 ], y: [ part.offset ]).flatten
|
22
23
|
else
|
23
|
-
|
24
|
+
border = border.composite(image, :over, x: [ part.offset ], y: [ 0 ]).flatten
|
24
25
|
end
|
25
26
|
end
|
26
|
-
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
result = block.call(canvas, border) if block_given?
|
29
|
+
|
30
30
|
x, y = result ? [ result.fetch(:x, 0), result.fetch(:y, 0) ] : [ 0, 0 ]
|
31
31
|
|
32
32
|
if vertical?
|
33
|
-
x = @position == :left ? x :
|
34
|
-
|
33
|
+
x = @position == :left ? x : canvas.width - @size - x
|
34
|
+
canvas.composite(border, :over, x: [ x ], y: [ 0 ]).flatten
|
35
35
|
else
|
36
|
-
y = @position == :top ? y :
|
37
|
-
|
36
|
+
y = @position == :top ? y : canvas.height - @size - y
|
37
|
+
canvas.composite(border, :over, x: [ 0 ], y: [ y ]).flatten
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
|
41
|
+
private
|
42
|
+
|
43
|
+
def hex_to_rgb(hex)
|
44
|
+
hex.match(/#(..)(..)(..)/)[1..3].map { |x| x.hex }
|
45
|
+
end
|
46
|
+
|
47
|
+
def dimensions(canvas)
|
48
|
+
if vertical?
|
49
|
+
[ @size, canvas.height ]
|
50
|
+
else
|
51
|
+
[ canvas.width, @size ]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def parts(canvas)
|
42
56
|
if @fill.is_a?(Array)
|
43
|
-
width, height = vertical? ? [ @size, (
|
57
|
+
width, height = vertical? ? [ @size, (canvas.height / @fill.size) ] : [ (canvas.width / @fill.size), @size ]
|
44
58
|
|
45
59
|
@fill.map.with_index do |item, index|
|
46
60
|
Part.new(
|
@@ -51,26 +65,12 @@ class JekyllOgImage::Element::Border < JekyllOgImage::Element::Base
|
|
51
65
|
)
|
52
66
|
end
|
53
67
|
else
|
54
|
-
length = vertical? ?
|
68
|
+
length = vertical? ? canvas.height : canvas.width
|
55
69
|
|
56
70
|
[ Part.new(rgb: hex_to_rgb(@fill), length: length, offset: 0) ]
|
57
71
|
end
|
58
72
|
end
|
59
73
|
|
60
|
-
private
|
61
|
-
|
62
|
-
def hex_to_rgb(hex)
|
63
|
-
hex.match(/#(..)(..)(..)/)[1..3].map { |x| x.hex }
|
64
|
-
end
|
65
|
-
|
66
|
-
def dimensions
|
67
|
-
if vertical?
|
68
|
-
[ @size, @canvas.height ]
|
69
|
-
else
|
70
|
-
[ @canvas.width, @size ]
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
74
|
def vertical?
|
75
75
|
@position == :left || @position == :right
|
76
76
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "vips"
|
4
|
+
|
5
|
+
class JekyllOgImage::Element::Canvas < JekyllOgImage::Element::Base
|
6
|
+
def initialize(width, height, color: "#ffffff")
|
7
|
+
@canvas = Vips::Image.black(width, height).ifthenelse([ 0, 0, 0 ], hex_to_rgb(color))
|
8
|
+
end
|
9
|
+
|
10
|
+
def image(source, **opts, &block)
|
11
|
+
image = JekyllOgImage::Element::Image.new(source, **opts)
|
12
|
+
@canvas = image.apply_to(@canvas, &block)
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def text(message, **opts, &block)
|
18
|
+
text = JekyllOgImage::Element::Text.new(message, **opts)
|
19
|
+
@canvas = text.apply_to(@canvas, &block)
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def border(width, **opts, &block)
|
25
|
+
border = JekyllOgImage::Element::Border.new(width, **opts)
|
26
|
+
@canvas = border.apply_to(@canvas, &block)
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def save(filename)
|
32
|
+
@canvas.write_to_file(filename)
|
33
|
+
end
|
34
|
+
end
|
@@ -1,33 +1,64 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "vips"
|
4
|
-
|
5
3
|
class JekyllOgImage::Element::Image < JekyllOgImage::Element::Base
|
6
|
-
def initialize(
|
7
|
-
@
|
4
|
+
def initialize(source, gravity: :nw, width:, height:, radius:)
|
5
|
+
@gravity = gravity
|
6
|
+
@source = source
|
7
|
+
@width = width
|
8
|
+
@height = height
|
9
|
+
@radius = radius
|
10
|
+
|
11
|
+
validate_gravity!
|
8
12
|
end
|
9
13
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
def apply_to(canvas, &block)
|
15
|
+
image = Vips::Image.new_from_buffer(@source, "")
|
16
|
+
image = round_corners(image) if @radius
|
17
|
+
|
18
|
+
result = block.call(canvas, image) if block_given?
|
19
|
+
|
20
|
+
if @width && @height
|
21
|
+
ratio = calculate_ratio(image, @width, @height, :min)
|
22
|
+
image = image.resize(ratio)
|
23
|
+
end
|
14
24
|
|
15
|
-
|
25
|
+
x, y = result ? [ result.fetch(:x, 0), result.fetch(:y, 0) ] : [ 0, 0 ]
|
16
26
|
|
17
|
-
|
27
|
+
composite_with_gravity(canvas, image, x, y)
|
18
28
|
end
|
19
29
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
30
|
+
private
|
31
|
+
|
32
|
+
VALID_GRAVITY.each do |gravity|
|
33
|
+
define_method("gravity_#{gravity}?") do
|
34
|
+
@gravity == gravity
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def round_corners(image)
|
39
|
+
mask = %(
|
40
|
+
<svg viewBox="0 0 #{image.width} #{image.height}">
|
41
|
+
<rect
|
42
|
+
rx="#{@radius}"
|
43
|
+
ry="#{@radius}"
|
44
|
+
x="0"
|
45
|
+
y="0"
|
46
|
+
width="#{image.width}"
|
47
|
+
height="#{image.height}"
|
48
|
+
fill="#fff"
|
49
|
+
/>
|
50
|
+
</svg>
|
51
|
+
)
|
26
52
|
|
27
|
-
|
53
|
+
mask = Vips::Image.new_from_buffer(mask, "")
|
54
|
+
image.bandjoin mask[3]
|
28
55
|
end
|
29
56
|
|
30
|
-
def
|
31
|
-
|
57
|
+
def calculate_ratio(image, width, height, mode)
|
58
|
+
if mode == :min
|
59
|
+
[ width.to_f / image.width, height.to_f / image.height ].min
|
60
|
+
else
|
61
|
+
[ width.to_f / image.width, height.to_f / image.height ].max
|
62
|
+
end
|
32
63
|
end
|
33
64
|
end
|
@@ -1,45 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JekyllOgImage::Element::Text < JekyllOgImage::Element::Base
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(canvas, message, gravity: :nw, width: nil, dpi: nil, color: "#000000", font: nil)
|
7
|
-
@canvas = canvas
|
4
|
+
def initialize(message, gravity: :nw, width: nil, dpi: nil, color: "#000000", font: nil)
|
5
|
+
@message = message
|
8
6
|
@gravity = gravity
|
7
|
+
@width = width
|
8
|
+
@dpi = dpi
|
9
|
+
@color = color
|
10
|
+
@font = font
|
9
11
|
|
10
12
|
validate_gravity!
|
13
|
+
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
def apply_to(canvas, &block)
|
16
|
+
text = Vips::Image.text(@message,
|
17
|
+
font: @font,
|
18
|
+
width: @width,
|
19
|
+
dpi: @dpi,
|
16
20
|
wrap: :word,
|
17
21
|
align: :low
|
18
22
|
)
|
19
23
|
|
20
|
-
|
21
|
-
.new_from_image(hex_to_rgb(color))
|
24
|
+
text = text
|
25
|
+
.new_from_image(hex_to_rgb(@color))
|
22
26
|
.copy(interpretation: :srgb)
|
23
27
|
.bandjoin(text)
|
24
|
-
end
|
25
28
|
|
26
|
-
|
27
|
-
|
29
|
+
result = block.call(canvas, text) if block_given?
|
30
|
+
|
28
31
|
x, y = result ? [ result.fetch(:x, 0), result.fetch(:y, 0) ] : [ 0, 0 ]
|
29
32
|
|
30
|
-
|
31
|
-
@canvas.composite(@text, :over, x: [ x ], y: [ y ]).flatten
|
32
|
-
elsif gravity_ne?
|
33
|
-
x = @canvas.width - @text.width - x
|
34
|
-
@canvas.composite(@text, :over, x: [ x ], y: [ y ]).flatten
|
35
|
-
elsif gravity_sw?
|
36
|
-
y = @canvas.height - @text.height - y
|
37
|
-
@canvas.composite(@text, :over, x: [ x ], y: [ y ]).flatten
|
38
|
-
elsif gravity_se?
|
39
|
-
x = @canvas.width - @text.width - x
|
40
|
-
y = @canvas.height - @text.height - y
|
41
|
-
@canvas.composite(@text, :over, x: [ x ], y: [ y ]).flatten
|
42
|
-
end
|
33
|
+
composite_with_gravity(canvas, text, x, y)
|
43
34
|
end
|
44
35
|
|
45
36
|
private
|
@@ -49,10 +40,4 @@ class JekyllOgImage::Element::Text < JekyllOgImage::Element::Base
|
|
49
40
|
@gravity == gravity
|
50
41
|
end
|
51
42
|
end
|
52
|
-
|
53
|
-
def validate_gravity!
|
54
|
-
unless VALID_GRAVITY.include?(@gravity)
|
55
|
-
raise ArgumentError, "Invalid gravity: #{@gravity.inspect}"
|
56
|
-
end
|
57
|
-
end
|
58
43
|
end
|
@@ -35,37 +35,49 @@ class JekyllOgImage::Generator < Jekyll::Generator
|
|
35
35
|
def generate_image_for_post(site, post, path)
|
36
36
|
date = post.date.strftime("%B %d, %Y")
|
37
37
|
|
38
|
-
|
38
|
+
canvas = JekyllOgImage::Element::Canvas.new(1200, 600)
|
39
39
|
.border(JekyllOgImage.config.border_bottom["width"],
|
40
40
|
position: :bottom,
|
41
41
|
fill: JekyllOgImage.config.border_bottom["fill"]
|
42
42
|
)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
43
|
+
|
44
|
+
if JekyllOgImage.config.image
|
45
|
+
canvas = canvas.image(
|
46
|
+
File.read(File.join(site.config["source"], JekyllOgImage.config.image)),
|
47
|
+
gravity: :ne,
|
48
|
+
width: 150,
|
49
|
+
height: 150,
|
50
|
+
radius: 50
|
51
|
+
) { |_canvas, _text| { x: 80, y: 100 } }
|
52
|
+
end
|
53
|
+
|
54
|
+
canvas = canvas.text(post.data["title"],
|
55
|
+
width: JekyllOgImage.config.image ? 870 : 1040,
|
56
|
+
color: "#2f313d",
|
57
|
+
dpi: 400,
|
58
|
+
font: "Helvetica, Bold"
|
59
|
+
) { |_canvas, _text| { x: 80, y: 100 } }
|
60
|
+
|
61
|
+
canvas = canvas.text(date,
|
62
|
+
gravity: :sw,
|
63
|
+
color: "#535358",
|
64
|
+
dpi: 150,
|
65
|
+
font: "Helvetica, Regular"
|
66
|
+
) { |_canvas, _text| { x: 80, y: post.data["tags"].any? ? 150 : 100 } }
|
55
67
|
|
56
68
|
if post.data["tags"].any?
|
57
69
|
tags = post.data["tags"].map { |tag| "##{tag}" }.join(" ")
|
58
70
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
71
|
+
canvas = canvas.text(tags,
|
72
|
+
gravity: :sw,
|
73
|
+
color: "#535358",
|
74
|
+
dpi: 150,
|
75
|
+
font: "Helvetica, Regular"
|
76
|
+
) { |_canvas, _text| { x: 80, y: 100 } }
|
65
77
|
end
|
66
78
|
|
67
79
|
if JekyllOgImage.config.domain
|
68
|
-
|
80
|
+
canvas = canvas.text(JekyllOgImage.config.domain, gravity: :se, color: "#535358", dpi: 150, font: "Helvetica, Regular") do |_canvas, _text|
|
69
81
|
{
|
70
82
|
x: 80,
|
71
83
|
y: post.data["tags"].any? ? 150 : 100
|
@@ -73,6 +85,6 @@ class JekyllOgImage::Generator < Jekyll::Generator
|
|
73
85
|
end
|
74
86
|
end
|
75
87
|
|
76
|
-
|
88
|
+
canvas.save(path)
|
77
89
|
end
|
78
90
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-og-image
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Alexandrov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll-seo-tag
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- lib/jekyll_og_image/config.rb
|
109
109
|
- lib/jekyll_og_image/element/base.rb
|
110
110
|
- lib/jekyll_og_image/element/border.rb
|
111
|
+
- lib/jekyll_og_image/element/canvas.rb
|
111
112
|
- lib/jekyll_og_image/element/image.rb
|
112
113
|
- lib/jekyll_og_image/element/text.rb
|
113
114
|
- lib/jekyll_og_image/generator.rb
|