MiniMagickでマシュマロのマネをしてみる

## はじめに

[Qiita](https://qiita.com/)や[はてなブログ](https://hatenablog.com/)の画像のない記事をシェアするときに、タイトルが入ったOGP画像が出るようになったのを見ていて、ずっと真似したいと思ってました。
[マシュマロ](https://marshmallow-qa.com/)でコメントが画像になっていて、自分の返事のツイートにつけられているのを見て、うまいなぁ、と感激し、自分もマネしてみたいと思い、作ってみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14380/edfa5fd1-11de-d203-ac41-d75e350294f7.png)

## サンプルアプリケーション

サンプルアプリケーションは、ユーザが好きな文章を投稿できる、シンプルなものです。(基本的には`rails g scaffold post body:string`です。)
これに、投稿をシェアするツイートに画像をつけて上げるために、いろいろしています。

ソース
https://github.com/ken1flan/minimagick_ogp

heroku
https://minimagick-ogp.herokuapp.com/posts

## 詳細

### ツイートボタン

twitterで画像を使った情報をシェアするときに2つ方法があると思います。

- 画像を添付してツイート
- リンクのOGPによるプレビュー

先の画像を添付する形が、普通にツイートするときには簡単だと思って調べたのですが、よくあるツイートボタンはただの、簡単に画像を添付することができなそうだったので、OGPを使った方法にすることにしました。

```erb:app/views/posts/show.html.erb
<!-- 省略 -->
<p>
  <%= render 'common/twitter_share_button', url: post_url(@post), message: 'ありがとうございます！' %>
</p>
<!-- 省略 -->
```

```erb:app/views/common/_twitter_share_button.html.erb
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-text="<%= message %>" data-url="<%= url %>" data-show-count="false">Tweet</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
```


### ツイッターカードと画像の縦横比

シェアしてもらうページのメタタグで`twitter:card`を設定しておくとプレビューの形を選べます。
これによって、OGPの画像の縦横比を変えたほうがよいです。せっかくテキストを埋め込んだ画像を合成しても、見切れてしまっては伝わらないですから…。

ここでは画像が大きめに出る `summary_large_image` を設定し、合成する画像のサイズは`640x315`にしました。


[サンプル](https://github.com/ken1flan/minimagick_ogp/blob/main/app/views/posts/show.html.erb#L2)は[meta-tags](https://github.com/kpumuk/meta-tags)を使ったものです。

```erb:app/views/posts/show.html.erb
<%
  set_meta_tags title: "post##{@post.id}", og: { title: "post##{@post.id}", description: @post.body, image: "#{request.base_url}/#{@post.image_path}" }, twitter: { card: 'summary_large_image' }
%>
  :
 (省略)
```

### フォント

画像にテキストを書き込むので、そのときのフォントが必要になります。
必然的に配布してしまうことになるので、画像に書き込んだものを配布しても大丈夫なライセンスで提供されているものを探さなくてはなりません。
サンプルでは、IPAexフォントを使っています。

これを [`app/assets/fonts`](https://github.com/ken1flan/minimagick_ogp/tree/main/app/assets/fonts/ipaexg00401)に配置しています。

### 画像合成用のクラス

投稿`Post`に対して、画像を合成するものなので、[`Post::Image`](https://github.com/ken1flan/minimagick_ogp/blob/main/app/models/post/image.rb)という名前にしました。

```ruby:app/models/post/image.rb
class Post::Image
  # (省略)
  attr_reader :post

  def initialize(post)
    @post = post
  end
  # (省略)
end
```

これを[`Post`](https://github.com/ken1flan/minimagick_ogp/blob/main/app/models/post.rb)から`image_path`とすると合成された画像のパスを得られるようにしています。

```ruby:app/models/post.rb
class Post < ApplicationRecord
  delegate :path, to: :image, prefix: true

  def image
    @image ||= Post::Image.new(self)
  end
end
```

### テキストの整形

MiniMagickによるテキストの描画は、指定位置で改行するなどの機能がないので、自分で整形する必要があります。

画像のサイズは有限なので、表示できる一行の文字数と、行数を決め、それに合わせてテキストを整形していきます。


```ruby:app/models/post/image.rb
class Post::Image
  # (省略)
  MAX_ROWS = 5
  COLS = 20
  ROWS = 10
  OMMIT_MESSAGE = '…（省略させてください。）'
  # (省略)
  def formated_body
    lines = post.body.lines.map { |line| line.scan(/.{1,#{COLS}}/) }.flatten
    lines = lines[0, MAX_ROWS - 1].push(OMMIT_MESSAGE) if lines.size > MAX_ROWS
    lines.join('\n')
  end
  # (省略)
end
```

### 合成

画像を開いて、整形したテキストを描画しています。

```ruby:app/models/post/image.rb
class Post::Image
  FRAME_IMAGE_PATH = Rails.root.join('app/assets/images/flame.png')
  FONT_PATH = Rails.root.join('app/assets/fonts/ipaexg00401/ipaexg.ttf')
  FONT_SIZE = 25
  INTERLINE_SPACING = (FONT_SIZE * 0.5).round
  COLOR_CODE = '#252828'
  START_X = 65
  START_Y = 60
  # (省略)
  def image
    image = MiniMagick::Image.open(FRAME_IMAGE_PATH) # 枠の画像を開き
    image.combine_options do |c|
      c.gravity 'northwest'                 # 左上から
      c.pointsize FONT_SIZE                 # 指定のフォントサイズで
      c.font FONT_PATH                      # 指定のフォントで
      c.interline_spacing INTERLINE_SPACING # 指定の行間のスペースで
      c.stroke COLOR_CODE                   # 指定の色で
      c.annotate "+#{START_X}+#{START_Y},0", formated_body # 整形したテキストを指定位置から描画します
    end
  end
  # (省略)
end
```

## ツイートのプレビューのリセット

作った画像が気に入らないときもあると思います。ですが、最適化の関係と思われますが、プレビューがキャッシュされてしまって、変更しても反映されないことがあります。そのときにはカードバリデータでプレビューを見ると、取り直してくれます。

https://cards-dev.twitter.com/validator


## やってみて

OGPを使った場合だと、シェアしてもらうコンテンツにそもそもURLがないとダメなのがネック…。
普段からコンテンツをキレイに分割するようにしておきたいです。