Daiji256

HugoでOGP/Twitter Cardを自動生成したい

公開
更新

はじめに

Hugo v0.90.0から、images.Textにより画像に文字を合成した画像を生成できるようになりました。これにより、Qiitaとかみたいに画像中にタイトルとかを記述したOGP/Twitter Cardを自動生成できるようになりました。

QiitaのOGP

この記事では最終的に次のような画像が生成されるようにします。

この記事の方法で生成されるOGP

準備

まずHugoを0.90.0以上にアップデートしてください。文字を合成する前の元画像を用意します。Twitter CardのLarge Summary Imageの推奨サイズが800 x 418 pxらしいのでそのサイズで用意しました1。

OGPの元画像

画像生成

テキストと書いた画像を生成する場合次のようにしたら良いです。この例ではGoogle FontsのGitHubからresources.GetRemoteを使ってNotoSansJP-Bold.otfを読み取りフォントをNotoSansJP Boldにしています。OGPの元画像として、static/images/ogp/ogp.pngにある画像を読み込んでいます。static/にあるファイルにアクセスするためにconfig.tomlでassetDir = "static"と設定してます。(images.Text ... (dict ...))は見ての通りテキストの位置や大きさなどを設定しています。最後に$img.RelPermalinkで画像のリンクを取得します。

static/images/ogp/...にある画像を参照することで、hugoを実行した時に生成される画像はimages/ogp/...になるので都合がいいので僕はこうしてます。

{{ $font := resources.GetRemote "https://github.com/google/fonts/raw/main/ofl/notosansjp/NotoSansJP-Bold.otf" }}
{{ $img := resources.Get "images/ogp/ogp.png" }}
{{ $img = $img | images.Filter
  (images.Text "テキスト" (dict
    "color" "#ff3300"
    "size" 32
    "x" 50 "y" 200
    "font" $font
  ))
}}
<img src="{{ $img.RelPermalink }}" />

英文のようにスペースが入る文章なら、改行処理を行ってくれますが、和文のようにスペースが入らない文章では最初に改行が入ってそのままはみ出してしまいます。

英文の場合和文の場合
英文が改行されて表示されているOGP和文が改行されずに表示されているOGP

そこで、長いタイトルの場合はみ出す分を切り取りこのようにします。

和文のはみ出した部分を切り取り表示したOGP

実際のソースコードを使って説明します。記事のタイトル以外に、ブログ名やカテゴリについても書きます。

4—8行目ではタイトルが長い場合に切り取っています。標準のタイトル文字数を22として、タイトルに英字を入れたことを考慮して手動で文字数を設定できるようにしています(5行目)。

dateは.Date.Formatで形式を指定します。categoriesとtagsはループでカンマ区切りの1つのStringにまとめます。

$title、$categoriesとかを複数の(images.Text ...)を使って、同時に書きます。

{{ $font := resources.GetRemote "https://github.com/google/fonts/raw/main/ofl/notosansjp/NotoSansJP-Bold.otf" }}
{{ $img := resources.Get "images/ogp/ogp-post.png" }}
{{ $title := .Title }}
{{ $strlen := 22 }}
{{ if .Params.strlen }}{{ $strlen = .Params.strlen }}{{ end }}
{{ if gt (strings.RuneCount $title) (add $strlen 1) }}
  {{ $title = printf "%s…" (substr $title 0 $strlen) }}
{{ end }}
{{ $date := .Date.Format "2006 年 1 月 2 日" }}
{{ $categories := "" }}
{{ range .Params.categories }}{{ $categories = printf "%s, %s" $categories . }}{{ end }}
{{ $categories = substr $categories 2 }}
{{ $tags := "" }}
{{ range .Params.tags }}{{ $tags = printf "%s, %s" $tags . }}{{ end }}
{{ $tags = substr $tags 2 }}
{{ $img = $img | images.Filter
  (images.Text .Site.Title (dict
    "color" "#333"
    "size" 32
    "x" 90 "y" 19
    "font" $font
  ))
  (images.Text $title (dict
    "color" "#333"
    "size" 32
    "x" 25 "y" 135
    "font" $font
  ))
  (images.Text $date (dict
    "color" "#666"
    "size" 24
    "x" 80 "y" 230
    "font" $font
  ))
  (images.Text $categories (dict
    "color" "#666"
    "size" 24
    "x" 80 "y" 280
    "font" $font
  ))
  (images.Text $tags (dict
    "color" "#666"
    "size" 24
    "x" 80 "y" 330
    "font" $font
  ))
}}

このように画像が出力されます。タイトルに英数字が入っているため、文字数の割に幅が狭いので、strlen: 34と設定しています。

表示する文字数を調整したOGP

OGP/Twitter Cardの設定

このブログではこんな感じでOGP/Twitter Cardを設定しています。

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@Daiji256" />
<meta name="twitter:title" content="{{ .Title }} - {{ .Site.Title }}" />
<meta name="twitter:image" content="{{ $img.RelPermalink | absURL }}" />
<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:type" content="article" />
<meta property="og:title" content="{{ .Title }} - {{ .Site.Title }}" />
<meta property="og:description" content="{{ .Summary }}" />
<meta property="og:site_name" content="{{ .Site.Title }}" />
<meta property="og:image" content="{{ $img.RelPermalink | absURL }}" />

おまけ

ホームやタグのページのような投稿日やカテゴリとかと無縁なページでは以下のように画像を生成するようにしました。

元の画像出力画像
ホームなどのOGPのための元画像ホームのOGP

このようにホーム等のページでは.Params.tagsが設定されないので、それを判定して2種類の画像生成を分岐するようにしました。

{{ if or .Params.tags .Params.categories }}
  <!-- カテゴリ、タグがある場合 -->
{{ else }}
  <!-- カテゴリ、タグがない場合 -->
{{ end }}

文献

  1. Hugo releases/v0.90.0

脚注

  1. OGPの推奨サイズは1200 x 630 pxらしいです。 ↩