# Android PdfDocumentで複雑絵文字を白黒Bitmapとして描画する
PdfDocument の Canvas.drawText() では、ZWJなどの複雑な絵文字が複数のglyphに分離することがある。
EmojiCompatの EmojiSpan を一度 Bitmap に描き、黒のアルファマスクにしてからPDFへ描くと、1つの見た目で白黒表示できる。
# drawText() だけでは複雑な絵文字が分離する
PdfDocument の Canvas.drawText() だけでは、複雑な絵文字が複数のglyphに分離することがある。
EmojiSpan を利用すると、EmojiCompatが解決した1つの見た目を使える。
# 文字の種類ごとに描画方法を分ける
| 文字の種類 | 描画方法 |
|---|---|
| 通常文字 | Canvas.drawText() で描画する |
| 単純な絵文字 | 白黒絵文字フォントの getTextPath() を使える |
| ZWJ、国旗、keycapなどの複雑な絵文字 | EmojiSpan を一度 Bitmap に描画する |
Bitmap は、黒のアルファマスクに変換してからPDFへ描画する。
# EmojiSpan をBitmapに描いてアルファマスクとしてPDFへ描画する
このコードは、EmojiCompatの初期化完了後に呼び出す。
依存ライブラリとして androidx.emoji2:emoji2 と androidx.emoji2:emoji2-bundled が必要である。
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.pdf.PdfDocument;
import android.text.Spanned;
import androidx.emoji2.text.EmojiCompat;
import androidx.emoji2.text.EmojiSpan;
import java.io.IOException;
import java.io.OutputStream;
public final class EmojiBitmapPdf {
public static void write(OutputStream output) throws IOException {
if (EmojiCompat.get().getLoadState() != EmojiCompat.LOAD_STATE_SUCCEEDED) {
throw new IllegalStateException("EmojiCompat の初期化完了後に実行してください");
}
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize(72);
Bitmap emoji = toMonochromeBitmap("😶🌫️", paint);
PdfDocument document = new PdfDocument();
PdfDocument.Page page = document.startPage(
new PdfDocument.PageInfo.Builder(300, 200, 1).create());
page.getCanvas().drawBitmap(emoji, 32, 32, paint);
document.finishPage(page);
document.writeTo(output);
document.close();
emoji.recycle();
}
private static Bitmap toMonochromeBitmap(String text, Paint paint) {
Spanned spanned = (Spanned) EmojiCompat.get().process(text);
EmojiSpan span = spanned.getSpans(0, spanned.length(), EmojiSpan.class)[0];
int start = spanned.getSpanStart(span);
int end = spanned.getSpanEnd(span);
Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
int width = span.getSize(paint, spanned, start, end, metrics);
Bitmap bitmap = Bitmap.createBitmap(width, metrics.descent - metrics.ascent,
Bitmap.Config.ARGB_8888);
span.draw(new Canvas(bitmap), spanned, start, end, 0, 0,
-metrics.ascent, bitmap.getHeight(), paint);
for (int y = 0; y < bitmap.getHeight(); y++) {
for (int x = 0; x < bitmap.getWidth(); x++) {
int color = bitmap.getPixel(x, y);
int alpha = Color.alpha(color);
int darkness = 255 - (Color.red(color) * 299
+ Color.green(color) * 587 + Color.blue(color) * 114) / 1000;
bitmap.setPixel(x, y, Color.argb(alpha * darkness / 255, 0, 0, 0));
}
}
return bitmap;
}
}
# Bitmap化すると選択とコピーができなくなる
この方法では、絵文字はPDF上で画像として扱われる。 そのため、選択やコピーは維持されない。 選択とコピーを残す方法は、2026年5月31日の記事「Android PdfDocumentでEmojiCompatの絵文字をテキストとして描画する」で扱う。
実装時は次の点に対応する。
- 変換後の
Bitmapは、必要に応じて透明余白をトリミングする - 同じ絵文字を繰り返し描画する場合は、文字列と文字サイズをキーにして
Bitmapをキャッシュする EmojiSpanを得られない場合だけ、白黒絵文字フォントやシステム絵文字へフォールバックする
筆者の考えでは、複雑な絵文字を1つの見た目でPDFに出したい場合、この方法は実用的な選択肢である。