diff --git a/Gemfile b/Gemfile index af8e1eeab01..1cd59d50896 100644 --- a/Gemfile +++ b/Gemfile @@ -35,3 +35,8 @@ gem "wdm", "~> 0.1.0" if Gem.win_platform? gem "webrick", "~> 1.7" + +# Used in _plugins/social_images.rb +gem "chunky_png" +gem "cairo" +gem "rsvg2" diff --git a/Gemfile.lock b/Gemfile.lock index 20443298663..c5fceee0c73 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,14 @@ GEM addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) asciidoctor (2.0.15) + cairo (1.17.9) + native-package-installer (>= 1.0.3) + pkg-config (>= 1.2.2) + red-colors + cairo-gobject (4.1.7) + cairo (>= 1.16.2) + glib2 (= 4.1.7) + chunky_png (1.4.0) colorator (1.1.0) concurrent-ruby (1.1.8) em-websocket (0.5.2) @@ -11,7 +19,18 @@ GEM http_parser.rb (~> 0.6.0) eventmachine (1.2.7) ffi (1.15.0) + fiddle (1.1.1) forwardable-extended (2.6.0) + gdk_pixbuf2 (4.1.7) + gio2 (= 4.1.7) + gio2 (4.1.7) + fiddle + gobject-introspection (= 4.1.7) + glib2 (4.1.7) + native-package-installer (>= 1.0.3) + pkg-config (>= 1.3.5) + gobject-introspection (4.1.7) + glib2 (= 4.1.7) http_parser.rb (0.6.0) i18n (1.8.10) concurrent-ruby (~> 1.0) @@ -53,19 +72,27 @@ GEM listen (3.5.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + matrix (0.4.2) mercenary (0.4.0) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) + native-package-installer (1.1.5) pathutil (0.16.2) forwardable-extended (~> 2.6) + pkg-config (1.5.1) public_suffix (4.0.6) rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) + red-colors (0.3.0) + matrix rexml (3.2.5) rouge (3.26.0) + rsvg2 (4.1.7) + cairo-gobject (= 4.1.7) + gdk_pixbuf2 (= 4.1.7) safe_yaml (1.0.5) sassc (2.4.0) ffi (~> 1.9) @@ -78,14 +105,17 @@ PLATFORMS ruby DEPENDENCIES + cairo + chunky_png jekyll (~> 4.1.1) jekyll-archives jekyll-asciidoc jekyll-feed (~> 0.6) jekyll-paginate-v2 minima (~> 2.0) + rsvg2 tzinfo-data webrick (~> 1.7) BUNDLED WITH - 2.2.16 + 2.3.7 diff --git a/_layouts/base.html b/_layouts/base.html index 62d83ff8d99..409be9f8183 100755 --- a/_layouts/base.html +++ b/_layouts/base.html @@ -34,7 +34,7 @@ - + {% if page.layout == 'guides' or page.layout == 'guides-index' %} {%assign canonical_url = page.url | replace_regex: '^/version/[^/]+', '' %} {% else %} diff --git a/_plugins/assets/quarkus_card_blank.png b/_plugins/assets/quarkus_card_blank.png new file mode 100644 index 00000000000..4b74b1102e0 Binary files /dev/null and b/_plugins/assets/quarkus_card_blank.png differ diff --git a/_plugins/social_images.rb b/_plugins/social_images.rb new file mode 100644 index 00000000000..4aef5dbf5ae --- /dev/null +++ b/_plugins/social_images.rb @@ -0,0 +1,123 @@ +require 'chunky_png' +require 'cairo' +require 'rsvg2' + +module Jekyll + # Generates social images for blog posts and guides + module SocialImages + def social_image(text, page_path) + # If text is not empty, return it + if text.nil? || text.empty? + # If page_path contains "guides/", return the social image path + if page_path.include?('guides/') + return "/assets/images/social/#{File.basename(page_path, '.adoc')}.png" + else + return "/assets/images/quarkus_card.png" + end + else + text + end + end + end + + class GenerateSocialImagesGenerator < Generator + def generate(site) + guides = Dir.glob(File.join(site.source, '_guides', '*.adoc')) + output_dir = 'assets/images/social' + FileUtils.mkdir_p(File.join(site.dest, output_dir)) + + guides.each do |guide_file| + basename = File.basename(guide_file, '.adoc') + if basename.start_with?('_') + next + end + title = extract_title(guide_file) + output_file = File.join(site.dest, output_dir, "#{basename}.png") + # Skip if the file already exists + if File.exist?(output_file) + next + end + + # Generate the SVG image + svg_image_str = generate_svg_string(title) + + # Create a Cairo surface and context for the PNG image (must be smaller than 600x330) + surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, 600, 250) + context = Cairo::Context.new(surface) + + # Load and render the SVG onto the Cairo context + svg = RSVG::Handle.new_from_data(svg_image_str) + context.render_rsvg_handle(svg) + + # Save the Cairo surface to a PNG file + b = StringIO.new + surface.write_to_png(b) + + # Compose the generated image with the template image + png_image = ChunkyPNG::Image.from_file('_plugins/assets/quarkus_card_blank.png') + # Change the last parameters to change the position of the generated image + png_image.compose!(ChunkyPNG::Image.from_blob(b.string), 0, 80) + + Jekyll.logger.info("Generating social image to #{output_file}") + # Save the composed image to the output file + png_image.save(output_file) + end + end + + def split_text_into_lines(text) + lines = [] + words = text.split(' ') + current_line = '' + + words.each do |word| + if current_line.length + word.length <= 32 + current_line += (current_line == '' ? '' : ' ') + word + else + lines.push(current_line) + current_line = word + end + end + + lines.push(current_line) unless current_line.empty? + + lines + end + + private + + def generate_svg_string(title) + idx = 90 + font_size = 30 + tspan_elements = '' + split_text_into_lines(title).each_with_index do |line, index| + tspan_elements += "#{line}" + idx += font_size + 10 + end + " + + + + #{tspan_elements} + + + " + end + + def extract_title(adoc_file) + title = '' + File.open(adoc_file, 'r') do |file| + file.each_line do |line| + if line.start_with? '=' + title = line[2..].strip + break + end + end + end + title + end + end +end + +Liquid::Template.register_filter(Jekyll::SocialImages)