Skip to content

Commit 11e8f9a

Browse files
committed
Generate social images
- Fixes #1723 Allow skipping social image generation Also generate social image for blog posts
1 parent 22fc70c commit 11e8f9a

File tree

7 files changed

+215
-22
lines changed

7 files changed

+215
-22
lines changed

Diff for: .github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
### In cases where you want to specify the cache key, enable the above 2 inputs
5555
### Follows the format here https://github.com/actions/cache
5656
#
57-
custom_opts: '--future --config _config.yml,_only_latest_guides_config.yml'
57+
custom_opts: '--future --config _config.yml,_only_latest_guides_config.yml,_nosocialimages_config.yml'
5858
### If you need to specify any Jekyll build options, enable the above input
5959
### Flags accepted can be found here https://jekyllrb.com/docs/configuration/options/#build-command-options
6060

Diff for: Gemfile

+6
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,9 @@ gem "wdm", "~> 0.1.0" if Gem.win_platform?
3535

3636

3737
gem "webrick", "~> 1.7"
38+
39+
# Used in _plugins/social_images.rb
40+
gem "chunky_png", "~> 1.4.0"
41+
gem 'rsvg2', '~> 4.1.7'
42+
gem "cairo", "~> 1.17.9"
43+
gem "rake", "~> 13.0.1"

Diff for: Gemfile.lock

+52-20
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
GEM
22
remote: https://rubygems.org/
33
specs:
4-
addressable (2.8.0)
5-
public_suffix (>= 2.0.2, < 5.0)
6-
asciidoctor (2.0.15)
4+
addressable (2.8.4)
5+
public_suffix (>= 2.0.2, < 6.0)
6+
asciidoctor (2.0.20)
7+
cairo (1.17.9)
8+
native-package-installer (>= 1.0.3)
9+
pkg-config (>= 1.2.2)
10+
red-colors
11+
cairo-gobject (4.1.7)
12+
cairo (>= 1.16.2)
13+
glib2 (= 4.1.7)
14+
chunky_png (1.4.0)
715
colorator (1.1.0)
8-
concurrent-ruby (1.1.8)
9-
em-websocket (0.5.2)
16+
concurrent-ruby (1.2.2)
17+
em-websocket (0.5.3)
1018
eventmachine (>= 0.12.9)
11-
http_parser.rb (~> 0.6.0)
19+
http_parser.rb (~> 0)
1220
eventmachine (1.2.7)
13-
ffi (1.15.0)
21+
ffi (1.15.5)
22+
fiddle (1.1.1)
1423
forwardable-extended (2.6.0)
15-
http_parser.rb (0.6.0)
16-
i18n (1.8.10)
24+
gdk_pixbuf2 (4.1.7)
25+
gio2 (= 4.1.7)
26+
gio2 (4.1.7)
27+
fiddle
28+
gobject-introspection (= 4.1.7)
29+
glib2 (4.1.7)
30+
native-package-installer (>= 1.0.3)
31+
pkg-config (>= 1.3.5)
32+
gobject-introspection (4.1.7)
33+
glib2 (= 4.1.7)
34+
http_parser.rb (0.8.0)
35+
i18n (1.14.1)
1736
concurrent-ruby (~> 1.0)
1837
jekyll (4.1.1)
1938
addressable (~> 2.4)
@@ -35,57 +54,70 @@ GEM
3554
jekyll-asciidoc (3.0.0)
3655
asciidoctor (>= 1.5.0)
3756
jekyll (>= 3.0.0)
38-
jekyll-feed (0.15.1)
57+
jekyll-feed (0.17.0)
3958
jekyll (>= 3.7, < 5.0)
4059
jekyll-paginate-v2 (3.0.0)
4160
jekyll (>= 3.0, < 5.0)
42-
jekyll-sass-converter (2.1.0)
61+
jekyll-sass-converter (2.2.0)
4362
sassc (> 2.0.1, < 3.0)
44-
jekyll-seo-tag (2.7.1)
63+
jekyll-seo-tag (2.8.0)
4564
jekyll (>= 3.8, < 5.0)
4665
jekyll-watch (2.2.1)
4766
listen (~> 3.0)
48-
kramdown (2.3.1)
67+
kramdown (2.4.0)
4968
rexml
5069
kramdown-parser-gfm (1.1.0)
5170
kramdown (~> 2.0)
5271
liquid (4.0.4)
53-
listen (3.5.1)
72+
listen (3.8.0)
5473
rb-fsevent (~> 0.10, >= 0.10.3)
5574
rb-inotify (~> 0.9, >= 0.9.10)
75+
matrix (0.4.2)
5676
mercenary (0.4.0)
5777
minima (2.5.1)
5878
jekyll (>= 3.5, < 5.0)
5979
jekyll-feed (~> 0.9)
6080
jekyll-seo-tag (~> 2.1)
81+
native-package-installer (1.1.5)
6182
pathutil (0.16.2)
6283
forwardable-extended (~> 2.6)
63-
public_suffix (4.0.6)
64-
rb-fsevent (0.11.0)
84+
pkg-config (1.5.1)
85+
public_suffix (5.0.1)
86+
rake (13.0.6)
87+
rb-fsevent (0.11.2)
6588
rb-inotify (0.10.1)
6689
ffi (~> 1.0)
90+
red-colors (0.3.0)
91+
matrix
6792
rexml (3.2.5)
68-
rouge (3.26.0)
93+
rouge (3.30.0)
94+
rsvg2 (4.1.7)
95+
cairo-gobject (= 4.1.7)
96+
gdk_pixbuf2 (= 4.1.7)
6997
safe_yaml (1.0.5)
7098
sassc (2.4.0)
7199
ffi (~> 1.9)
72100
terminal-table (1.8.0)
73101
unicode-display_width (~> 1.1, >= 1.1.1)
74-
unicode-display_width (1.7.0)
75-
webrick (1.7.0)
102+
unicode-display_width (1.8.0)
103+
webrick (1.8.1)
76104

77105
PLATFORMS
78106
ruby
79107

80108
DEPENDENCIES
109+
cairo (~> 1.17.9)
110+
chunky_png (~> 1.4.0)
81111
jekyll (~> 4.1.1)
82112
jekyll-archives
83113
jekyll-asciidoc
84114
jekyll-feed (~> 0.6)
85115
jekyll-paginate-v2
86116
minima (~> 2.0)
117+
rake (~> 13.0.1)
118+
rsvg2 (~> 4.1.7)
87119
tzinfo-data
88120
webrick (~> 1.7)
89121

90122
BUNDLED WITH
91-
2.2.16
123+
2.4.10

Diff for: _layouts/base.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<meta property="og:url" content="{{ page.url | prepend: site.url }}" />
3636
<meta property="og:title" content="{{ page.title }}{{ page_title_version_suffix }}" />
3737
<meta property="og:description" content="{% if page.description %}{{ page.description }}{% else %}{{ site.description }}{% endif %}" />
38-
<meta property="og:image" content="{{ '/assets/images/quarkus_card.png' | prepend: site.url }}" />
38+
<meta property="og:image" content="{{ page.social_image | social_image: page.path | prepend: site.url }}" />
3939
{% if page.layout == 'guides' or page.layout == 'guides-index' %}
4040
{%assign canonical_url = page.url | replace_regex: '^/version/[^/]+', '' %}
4141
{% else %}

Diff for: _nosocialimages_config.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
skip_social_images: true

Diff for: _plugins/assets/quarkus_card_blank.png

237 KB
Loading

Diff for: _plugins/social_images.rb

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
require 'chunky_png'
2+
require 'cairo'
3+
require 'rsvg2'
4+
5+
module Jekyll
6+
# Generates social images for blog posts and guides
7+
module SocialImages
8+
def social_image(text, page_path)
9+
# If text is not empty, return it
10+
if text.nil? || text.empty?
11+
if File.exist?("./assets/images/social/#{File.basename(page_path, '.adoc')}.png")
12+
return "/assets/images/social/#{File.basename(page_path, '.adoc')}.png"
13+
else
14+
return "/assets/images/quarkus_card.png"
15+
end
16+
else
17+
text
18+
end
19+
end
20+
end
21+
22+
class GenerateSocialImagesGenerator < Generator
23+
def generate(site)
24+
# Check if skip_social_images is set to true
25+
# If so, skip generating social images
26+
# This is useful when running the site locally
27+
if site.config['skip_social_images']
28+
Jekyll.logger.info('Skipping social image generation')
29+
return
30+
end
31+
generate_images(Dir.glob(File.join(site.source, '_posts', '*.adoc')), site)
32+
generate_images(Dir.glob(File.join(site.source, '_guides', '*.adoc')), site)
33+
end
34+
35+
def split_text_into_lines(text)
36+
lines = []
37+
words = text.split(' ')
38+
current_line = ''
39+
40+
words.each do |word|
41+
if current_line.length + word.length <= 32
42+
current_line += (current_line == '' ? '' : ' ') + word
43+
else
44+
lines.push(current_line)
45+
current_line = word
46+
end
47+
end
48+
49+
lines.push(current_line) unless current_line.empty?
50+
51+
lines
52+
end
53+
54+
private
55+
56+
def generate_images(files, site)
57+
output_dir = 'assets/images/social'
58+
FileUtils.mkdir_p(File.join(site.source, output_dir))
59+
60+
files.each do |guide_file|
61+
basename = File.basename(guide_file, '.adoc')
62+
if basename.start_with?('_')
63+
next
64+
end
65+
title = extract_title(guide_file)
66+
# Skip if title is empty
67+
if (title.nil? || title.empty?)
68+
next
69+
end
70+
output_file = File.join(site.source, output_dir, "#{basename}.png")
71+
# Skip if the file already exists
72+
if File.exist?(output_file)
73+
next
74+
end
75+
76+
Jekyll.logger.info("Generating social image for '#{title}' in #{output_file}")
77+
78+
# Generate the SVG image
79+
svg_image_str = generate_svg_string(title)
80+
81+
# Create a Cairo surface and context for the PNG image (must be smaller than 600x330)
82+
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, 600, 250)
83+
context = Cairo::Context.new(surface)
84+
85+
# Load and render the SVG onto the Cairo context
86+
svg = RSVG::Handle.new_from_data(svg_image_str)
87+
context.render_rsvg_handle(svg)
88+
89+
# Save the Cairo surface to a PNG file
90+
b = StringIO.new
91+
surface.write_to_png(b)
92+
93+
# Compose the generated image with the template image
94+
png_image = ChunkyPNG::Image.from_file('_plugins/assets/quarkus_card_blank.png')
95+
# Change the last parameters to change the position of the generated image
96+
png_image.compose!(ChunkyPNG::Image.from_blob(b.string), 0, 80)
97+
98+
# Save the composed image to the output file
99+
png_image.save(output_file)
100+
end
101+
end
102+
103+
def generate_svg_string(title)
104+
idx = 90
105+
font_size = 30
106+
tspan_elements = ''
107+
# Sanitize title
108+
title = title.gsub(/&/, '&amp;')
109+
title = title.gsub(/</, '&lt;')
110+
title = title.gsub(/>/, '&gt;')
111+
112+
split_text_into_lines(title).each_with_index do |line, index|
113+
tspan_elements += "<tspan x='50%' y='#{idx}'>#{line}</tspan>"
114+
idx += font_size + 10
115+
end
116+
"
117+
<svg width=\"600\" height=\"330\">
118+
<style>
119+
.title { fill: white; font-size: #{font_size}px; font-weight: bold; font-family:'Open Sans'}
120+
</style>
121+
<text x=\"50%\" y=\"50%\" text-anchor=\"middle\" class=\"title\" >
122+
#{tspan_elements}
123+
</text>
124+
</svg>
125+
"
126+
end
127+
128+
def extract_title(adoc_file)
129+
line_nr = 0
130+
File.readlines(adoc_file).each do |line|
131+
if line_nr == 0
132+
# If line does not start with --- break
133+
unless line.strip.start_with?('---')
134+
break
135+
end
136+
end
137+
if line_nr > 0 && line.strip.start_with?('---')
138+
break;
139+
end
140+
if line.strip.start_with?('title:')
141+
title = line.strip.sub('title:', '').strip
142+
# Remove quotes
143+
title = title.gsub(/\A[\"']|[\"']\z/, '')
144+
return title
145+
end
146+
line_nr += 1
147+
end
148+
doc = Asciidoctor.load_file(adoc_file, header_only: true, logger: NullLogger.new)
149+
doc.doctitle
150+
end
151+
end
152+
end
153+
154+
Liquid::Template.register_filter(Jekyll::SocialImages)

0 commit comments

Comments
 (0)