Class: Abqari::OgImageGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/abqari/og_image_generator.rb

Overview

Auto-generated OG share images. When og_images.auto: true in config/site.yml, the build emits a styled SVG card per content page at <page-url>og.svg — so a post at /posts/hello/ gets /posts/hello/og.svg. The head template's share_image resolution chain falls through to this URL when no explicit share_image: or image: is set on the page.

Output is SVG, not PNG. Modern social platforms (Facebook, LinkedIn, Mastodon, Discord, Slack) render SVG og:image fine in 2026; Twitter/X can be finicky, but its share cards have been generally improving on this front. Users that need PNG for max compatibility can convert via vips/magick — or supply their own hero, which always takes precedence.

Card layout:

1200×630 (standard OG dimensions, ~1.91:1 aspect)
theme-coloured gradient background (from config/site.yml's
manifest.theme_color — falls back to a tasteful default)
site title (small, top-left)
page title (large, centred, wrapped at ~22 chars)
author + date (small, bottom-left)

Why SVG instead of pre-rendered PNG? Three reasons:

- Zero external dependencies (vips/magick not required)
- Tiny output (~1-2 KB per page vs. 30-80 KB PNG)
- Themed via existing site colors — no separate design system

Constant Summary collapse

OG_WIDTH =
1200
OG_HEIGHT =
630
TITLE_CHARS_PER_LINE =

Approximate character widths at the chosen font size, used for naive word-wrapping. Sans-serif at 72px renders ~22 chars per line within the safe content area; lower for narrow lines.

22
TITLE_MAX_LINES =
4

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(site) ⇒ OgImageGenerator

Returns a new instance of OgImageGenerator.



43
44
45
# File 'lib/abqari/og_image_generator.rb', line 43

def initialize(site)
  @site = site
end

Class Method Details

.auto_card?(page) ⇒ Boolean

Single source of truth for "does this page get an auto card?". The head partial calls this before emitting the <page-url>og.svg og:image URL, and write below calls it before generating the file — sharing the predicate is what keeps the two from drifting (drift means pages advertising og:image files that 404).

Only real content Pages qualify. Generated pages (TaxonomyPage, PaginatedShard, RelatedIndexPage, PublisherShowPage) don't carry their own titles the same way and mostly shouldn't be SHARED directly; flat-file URLs (/404.html) can't host a sibling og.svg because their path isn't a directory. Pages opt out with og_image: false, and a page with its own share_image: or image: hero doesn't need a card (the hero IS the share image).

Returns:

  • (Boolean)


64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/abqari/og_image_generator.rb', line 64

def self.auto_card?(page)
  return false unless page.is_a?(Abqari::Page)
  return false unless page.url.to_s.end_with?('/')
  return false if page.title.to_s.empty?

  fm = page.frontmatter
  return false if fm['og_image'] == false
  return false unless fm['share_image'].to_s.empty?
  return false unless fm['image'].to_s.empty?

  true
end

Instance Method Details

#enabled?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/abqari/og_image_generator.rb', line 47

def enabled?
  @site.config.dig('og_images', 'auto') == true
end

#writeObject



77
78
79
80
81
82
83
84
85
# File 'lib/abqari/og_image_generator.rb', line 77

def write
  return unless enabled?

  Array(@site.pages).each do |page|
    next unless self.class.auto_card?(page)
    svg = render_svg(page)
    write_file(page, svg)
  end
end