HUGO v0.90.0で追加されたimages.TextでブログカードとOGP画像を自動生成する

Posts

HUGOのv0.90.0からimages.Textを使って、画像と文字を合成した画像を生成できるようになりました。
images.Textを使うと、記事ファイル内のテキスト情報を埋め込んだ画像を自動で生成できそうです。

この記事では、記事ファイルのFrontmatterの情報とimages.Textを使って、生成したブログカードの画像を一覧に表示したり、OGP画像として設定することを試してみました。

動作を確認した環境

HUGO v0.90.0

img.Textの使い方を確認する

img.Textは、Image Filtersの引数として渡すための関数です。
参考:ドキュメントのサンプル
img.Textに渡されたテキストリソースと指定した画像を、Image Filtersを使って合成します。

{{ $img := resources.Get "/images/background.png"}}
{{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
    "color" "#ffffff"
    "size" 60
    "linespacing" 2
    "x" 10
    "y" 20
))}}

実装のイメージ

画像ファイルは、Frontmatterのimageで指定し、テキストにはtitleを指定します。

posts/ramen.md
---
title: 福岡の美味しいラーメン
image: ramen.jpg
---

## 福岡といえばとんこつ

記事や画像ファイルは、次のように配置しました。

$ tree content
content
└── post
    ├── _index.md  # 一覧ページ
    ├── gourmet.jpg
    ├── ramen.jpg
    ├── ramen.md  # 記事ページ
    ├── udon.jpg
    └── udon.md  # 記事ページ

以降の説明では、hugo new theme THEME_NAMEで生成した空のテンプレートに実装することを想定しています。

一覧ページに記事情報から生成したブログカードを表示する

リストテンプレートの実装

それぞれの記事から、ブログカードの画像を作成し表示するリストページを作ります。

layouts/_default/list.html
{{- define "main" }}
<h1>{{ .Title }}</h1>
{{ $font := resources.Get "https://github.com/google/fonts/raw/main/ofl/notosansjp/NotoSansJP-Black.otf" }}
{{ range .RegularPages }}
<div>
  {{- $img := .Page.Parent.Resources.GetMatch (.Params.image) }}
  {{- $title := .Title }}
  {{- with $img }}
    {{- $options := images.Text $title (dict "color" "#eee" "size" 30 "y" 120 "font" $font) }}
    {{- $img = $img.Fill "600x315 Center"  | images.Filter $options }}
    <div><img src="{{ $img.RelPermalink }}" alt="ブログカード" ></div>
  {{- end }}
  <div>
    <a href="{{ .Permalink }}">{{ .Date.Format "2006-01-02" }} | {{ .Title }}</a>
  </div>
</div>
{{ end -}}
{{ end -}}
  • 6行目: Frontmatterのimage.Params.image)で指定されたパスから画像を取得する
  • 7, 9行目:テキスト情報には記事のタイトル(.Title)を使う
  • 10行目:images.Textで生成したテキストを元の画像に合成する

動作確認

http://localhost:1313/posts/を確認してみます。

一覧ページの画面例

それぞれの記事のタイトルを埋め込んだブログカードの画像が一覧に表示されていました。

生成した画像をOGP画像に設定する

head内に表示するテンプレートの実装

同様に、生成した画像のパスをOGPのメタ情報として指定します。

layouts/partials/head.html
<meta property="og:type" content="article">
<meta property="og:title" content="{{ .Title }}">
<meta property="og:description" content="{{ .Description }}">
<meta property="og:site_name" content="{{ .Site.Title }}">
{{- $font := resources.Get "https://github.com/google/fonts/raw/main/ofl/notosansjp/NotoSansJP-Black.otf" }}
{{- $img := "" }}
{{- if .IsPage }}{{- $img = .Page.Parent.Resources.GetMatch (.Params.image) }}
{{ else if .IsSection }}{{- $img = .Page.Resources.GetMatch (.Params.image) }}
{{ end }}
{{- $title := .Title }}
{{- with $img }}
  {{- $option := images.Text $title (dict "color" "#eee" "size" 30 "y" 120 "font" $font) }}
  {{- $img = $img.Fill "600x315 Center"  | images.Filter $option }}
  <meta property="og:image" content="{{ $img.RelPermalink }}">
{{ end -}}
  • 7, 8行目:記事ページと一覧ページでは、リソースにアクセスする方法が違います。そのため、それぞれに合った方法で画像を取得します。

動作確認

ローカルでOGP情報を確認できるChrome拡張Social Share Previewを使って、プレビューしてみます。

一覧ページのOGPのプレビュー(http://localhost:1313/posts/
一覧ページの OGP のプレビューの画面例

記事ページのOGPのプレビュー(http://localhost:1313/posts/udon/
記事ページの OGP のプレビューの画面例

それぞれ、記事の情報に合わせたOGP画像を設定できました。

気になったこと

今回、実装してみて気になったのは、次の点です。

  • テキストが長い場合、画像の幅に合わせてサイズを縮小してくれない
    • Frontmatterからフォントサイズや位置情報を渡せば解決しそう
  • フォントファイルを取得するときに、キャッシュが効いているか