Android WebView で明朝体フォントをアプリ内アセットから読み込む方法

これまでのあらすじ

  • Android は 4.0 以降から明朝体フォントが存在しない
  • Android OS の機能としてレンダリングされる View (TextView とか) なら setTypeface() を利用すれば任意のフォントを利用することができる
  • 一方で WebView は (あたりまえだが) レンダリングはウェブブラウザ的なエンジンを使っているので setTypeface() とか使えない
  • Web の文脈で任意のフォントを使うには WebFonts を使えばよい
  • WebView のキャッシュの効き具合が謎な割に、日本語フォントのような容量の大きいフォントファイルを何度も都度ダウンロードする羽目になるのは厳しいので、アプリ内にフォントファイルを同梱したい
  • 普通にやってみると (これまた当たり前だが) フォントファイルだけローカル (file:// とか android_assets://) から読み込もうとするとセキュリティポリシー違反で読み込めない
  • Base64 にして直接流し込む方法を見付けるも、 (またまた当たり前なんだけど) 数十 MB ある日本語フォントの Base64 を読み込ませるとメモリが足りなくてまともに動かない
  • ローカルファイルだからセキュリティポリシー違反になる! それなら HTTP Proxy を Android app 内で立ててそれ経由して書き換えるぞ!!! (暴言)

Q. じゃあどうすればいいのか?

解決方法

A. WebViewClientshouldInterceptRequest メソッドを実装しましょう。

  • WebViewClient  |  Android Developers
  • shouldInterceptRequest を実装すると、 WebView からの Request を乗っ取ることができる
  • WebFonts を読み込むために適当な URL を CSS から読み込ませるようにする
  • その URL とマッチする場合だけ、 Response をローカルで組み立てて WebView に返すことができる
  • マッチしないときは super class を呼び出せばよい
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
  return if (request.url.toString() == "https://0.0.0.0/NotoSerifJP-Regular.woff2")) {
    WebResourceResponse(
      "application/font-woff2",
      "UTF-8",
      200,
      "OK",
      mapOf(HEADER_ACCESS_CONTROL_ALLOW_ORIGIN to "*"),
      context.assets.open("NotoSerifJP-Regular.woff2")
    )
  } else {
    super.shouldInterceptRequest(view, request)
  }
}

そして、 NotoSerifJP-Regular.woff2 を呼び出すように CSS を書き換える。

@font-face {
  font-family: 'NotoSerifJP';
  src: url('https://0.0.0.0/NotoSerifJP-Regular.woff2');
}

うっかりこの仕組みが動かなくなった後に HTTP Request が漏れないようダミー URL として 0.0.0.0 を指定しているけど、自分が管理しているウェブサーバでウェブフォントを配信しているならそこに向けても良いかもしれない。

ただし GoogleFonts にするのはダメで、それはグリフごとに別々の woff ファイルを作って配信効率を良くしようとしているので、今回のような単一の woff ファイルでの置き換えが出来ないからである。

所感

解決してみれば、そういうものだなって感じがするけど、ググっても出てこないので非常にめんどくさい。

Android WebView で明朝体を表示したいが、ウェブフォントを直接指定するのでは困る人は結構いそうなものだけど…。

追記