【Hugo】images.TextでOGP画像を生成する
更新日:2025.04.19
作成日:2025.04.19
XTwitterのサムネイルなど
Open Graph protocol
の画像をimages.Text
で作成する。
英語だと自動でテキストを折り返してくれるが、日本語だと分かち書きされていないため折り返されない。
以下のTwitter Card Image Generatorは、指定したWidthで折り返し設定ができた。 Ladicle/tcardgen: Generate a TwitterCard(OGP) image for your Hugo posts.
images.Text
で同じことを実現するには、自分でHugo Templateを実装するしかない。
方針
- 等幅フォントを利用して一定の文字数を越えたら、折り返す
- 英単語であれば、途中で折り返されないようにする
実装
パーシャル関数で呼び出す。
head.html
...
{{ $image := partial "functions/getOgpImage.html" . }}
...
予め利用するフォントを用意する。今回は、UDEV Gothicを利用した。 yuru7/udev-gothic: UDEV Gothic は、ユニバーサルデザインフォントのBIZ UDゴシックと、 開発者向けフォントの JetBrains Mono を合成した、プログラミング向けフォントです。
折り返しは、手動で改行コードを付加する。
functions/getOgpImage.html
{{- $image := "" -}}
{{- $font := resources.Get "UDEVGothic-Regular.ttf" }}
{{- $title := partial "functions/splitTitleWithTab.html" .Title -}}
{{- $lines := partial "functions/getTitleLines.html" (dict
"Title" $title
"MaxLength" 30)
-}}
{{- $titleText := delimit $lines "\n" -}}
{{- $articleTitle := images.Text $titleText (dict
"font" $font
"color" "#333"
"size" 60
"x" 120
"y" 100
"linespacing" 28
)
-}}
{{- $author := images.Text (print "@meganii / " ( time.Format "2006-01-02" .Date)) (dict
"font" $font
"color" "#8D8D8D"
"size" 40
"x" 220
"y" 455
"linespacing" 2)
-}}
{{- $image = (resources.Get "images/template_blog.png"| images.Filter $articleTitle $author ) -}}
{{- $image = $image.Permalink -}}
{{- return $image -}}
英単語を抽出したかったので、日本語と英語、英語と日本語の間にタブを埋め込み、あとで判断するようにした。
functions/splitTitleWithTab.html
{{- /* 英単語の前後にタブを付加して返却する */ -}}
{{- $title := "" -}}
{{- $title = replaceRE `([\p{Han}\p{Hiragana}\p{Katakana}ー~〜「」『』【】()[]{}〈〉《》。、・…!?])([a-zA-Z0-9-])` "$1\t$2" . -}}
{{- $title = replaceRE `([a-zA-Z0-9-])([\p{Han}\p{Hiragana}\p{Katakana}ー~〜「」『』【】()[]{}〈〉《》。、・…!?])` "$1\t$2" $title -}}
{{- return $title -}}
functions/getTitleLines.html
{{- /* 英単語を検出して分割するロジック */ -}}
{{- $lines := slice -}}
{{- $maxLength := .MaxLength -}}
{{- $words := split .Title "\t" -}}
{{- $currentLine := "" -}}
{{- $currentLength := 0 -}}
{{- range $words -}}
{{- $word := . -}}
{{- $chunks := partial "functions/splitChunk.html" (dict "Word" $word) -}}
{{- range $chunks -}}
{{- $chunk := . -}}
{{- $count := partial "functions/countChars.html" $chunk -}}
{{- if le (add $currentLength $count) $maxLength -}}
{{- $currentLine = printf "%s%s" $currentLine $chunk -}}
{{- $currentLength = add $currentLength $count -}}
{{- else -}}
{{- /* 超える場合は現在の行を確定し、新しい行を開始 */ -}}
{{- if ne $currentLine "" -}}
{{- $lines = $lines | append $currentLine -}}
{{- end -}}
{{- $currentLine = $chunk -}}
{{- $currentLength = $count -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* 最後の行を追加 */ -}}
{{- if ne $currentLine "" -}}
{{- $lines = $lines | append $currentLine -}}
{{- end -}}
{{- return $lines -}}
英単語ならそのまま返却して、英単語以外なら文字列分割して返却する。
functions/splitChunk.html
{{- $chunk := "" -}}
{{- if findRE "^[a-zA-Z0-9-/ ]+$" .Word -}}
{{- $chunk = slice .Word -}}
{{- else -}}
{{- $chunk = split .Word "" -}}
{{- end -}}
{{- return $chunk -}}