• SIS Lab
  • >
  • note
  • >
  • 【Hugo】images.TextでOGP画像を生成する

【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 -}}