string2path パッケージで COLRv1 絵文字フォントを読めるようになりました - Technically, technophobic.

string2path パッケージで COLRv1 絵文字フォントを読めるようになりました

string2path は、文字列のアウトラインをプロット可能なデータフレームに変換する R パッケージです。

これは ttf-parser という Rust のライブラリを使っているのですが、パッケージのメンテナンスついでにバージョン上げておくかと思って CHANGELOG を確認していると、COLRv1 絵文字フォントを読めるようになっていることに気付きました。 ということで、せっかくなので対応させてみました。

こんな感じです。

library(string2path)

# COLRv1 フォント(後述)
ttf <- "~/Downloads/NotoColorEmoji-Regular.ttf"

d <- string2fill("👺", ttf)

ggplot(d) +
  geom_polygon(aes(x, y, group = triangle_id, fill = color)) +
  coord_equal() +
  theme_minimal() +
  scale_fill_identity() +
  ggtitle("判断が遅い")

COLRv1 とは?

フォントは詳しくないのですが、カラーの絵文字フォントにはビットマップフォントとベクターフォントがあり、COLRv1はベクターフォントです。 Chrome 98(2022年2月リリース)からサポートされています。ベクターフォントなのでデータが軽く、グラデーションなどビットマップ形式にはない表現もできるのが強みらしいです。

ちなみに、ややこしいことに、まだ世の中にはビットマップの絵文字フォント(CBDT/CBLC)も出回っているので、「string2path でカラー絵文字フォント読むぞ!」と思っても COLRv1 フォントじゃなくて読めないことがあります。たとえば、Noto Color Emoji は、Google Fonts で配布されているもの は COLRv1 ですが、GitHub レポジトリの README からリンクされているのはまだビットマップです。

また、冒頭のコードで、↓のように、システムにインストールされたフォントではなくダウンロードしたものを使っているのは、Arch Linux でパッケージングされているのがビットマップフォントの方だったからです。

ttf <- "~/Downloads/NotoColorEmoji-Regular.ttf"

このように、ちょっと混乱することがけっこうあるので、そのフォントデータがどういう形式のものなのか、確認して使うようにしましょう(かんたんな見分け方があるのかはよくわからず...)。

詳細

具体的にどういうデータが返ってくるのか見てみましょう。string2path には、

  • string2path(): 文字のアウトライン
  • string2stroke(): 文字のアウトラインのポリゴン
  • string2fill(): 文字のポリゴン

の 3 種類がありますが、どれも同じで、color emoji の場合は以下のように color というカラムが追加されます。

string2fill("💸", ttf)
#> # A tibble: 6,624 × 5
#>        x     y glyph_id triangle_id color    
#>    <dbl> <dbl>    <int>       <int> <chr>    
#>  1 0.358 0.402        1           0 #e0e0e0ff
#>  2 0.355 0.402        1           0 #e0e0e0ff
#>  3 0.351 0.402        1           0 #e0e0e0ff
#>  4 0.362 0.402        1           1 #e0e0e0ff
#>  5 0.358 0.402        1           1 #e0e0e0ff
#>  6 0.367 0.403        1           1 #e0e0e0ff
#>  7 0.371 0.405        1           2 #e0e0e0ff
#>  8 0.367 0.403        1           2 #e0e0e0ff
#>  9 0.378 0.407        1           2 #e0e0e0ff
#> 10 0.384 0.410        1           3 #e0e0e0ff
#> # ℹ 6,614 more rows
#> # ℹ Use `print(n = ...)` to see more rows

ggplot2 でプロットする場合、これを fillcolorマッピングして、スケールを scale_fill_identity() にすることで本来の色がプロットできます。

d <- string2fill("💸", ttf)
ggplot(d) +
  geom_polygon(aes(x, y, group = triangle_id, fill = color)) +
  coord_equal() +
  theme_minimal() +
  scale_fill_identity()

ちなみに、ggplot2 は geom_text() でカラー絵文字フォントをプロットすることもできますが*1、サイズをざっくり合わせて並べてみると以下のような感じです。右が geom_text() です。色が再現できてそうですね。

d2 <- data.frame(x = max(d$x) * 1.5, y = max(d$y) * 0.5)
ggplot(d) +
  geom_polygon(aes(x, y, group = triangle_id, fill = color)) +
  geom_text(data = d2, aes(x, y), label = "💸", size.unit = "in", size = 2.8) +
  coord_equal(xlim = c(0, 2)) +
  theme_minimal() +
  scale_fill_identity()

(おまけ)遊び方

color は、単純に色の値としてではなく、「同じ色を塗るグループの ID」として使うこともできます。 要は塗り絵です。例えば、ちょっとずつ色をずらしたものをアニメーションさせるとこんな感じになります。

イメージ的にはこういうコードです。

d <- string2fill("🐯", ttf)
orig <- unique(d$color)
other_colors <- c(...)

do_plot <- function(offset) {
  offset <- offset %% length(orig)
  pal <- c(orig, other_colors)
  pal <- pal[offset:(offset + length(orig))]
  names(pal) <- orig

  p <- ggplot(d) +
    geom_polygon(aes(x, y, group = triangle_id, fill = color)) +
    coord_equal() +
    theme_minimal() +
    scale_fill_manual(values = pal, guide = "none")

  plot(p)
}

制限

string2path パッケージは、COLRv1 の仕様のごく一部しかサポートしていません。 以下のような機能を使っているフォントはうまくレンダリングできません。

例えば、🔮の絵文字はグラデーションを使っているので、実際のレンダリングとは少し異なった色になります。 まあ水晶玉には見えるしいいかなと思ってるんですが、一応、実際の見え方が再現されるわけではないという点にご注意ください。

d <- string2fill("🔮", ttf)
d2 <- data.frame(x = max(d$x) * 1.5, y = max(d$y) * 0.5)
ggplot(d) +
  geom_polygon(aes(x, y, group = triangle_id, fill = color)) +
  geom_text(data = d2, aes(x, y), label = "🔮", size.unit = "in", size = 2.8) +
  coord_equal(xlim = c(0, 2)) +
  theme_minimal() +
  scale_fill_identity()

インストール方法

CRAN にはしばらくリリースする予定はありませんが、R-universe からインストールできます。気になった方は試してみてください。

install.packages("string2path",
  repos = c(
    yutannihilation = "https://yutannihilation.r-universe.dev",
    CRAN = "https://cloud.r-project.org"
  )
)

*1:環境によっては表示されないこともあります。グラフィックデバイスがサポートしている場合は、表示されます。ragg は大丈夫ですが、R の標準のグラフィックデバイスだと無理かもです