# Android PdfDocumentで複雑絵文字を白黒Bitmapとして描画する

Android の PdfDocument で、複雑な絵文字を1つの見た目として白黒表示する方法を整理します。

# 方針

  • 通常文字は Canvas.drawText() で描画します。
  • 単純な絵文字は、白黒絵文字フォントの getTextPath() を使用できます。
  • ZWJ、国旗、keycap などの複雑な絵文字は、EmojiCompatEmojiSpan を一度 Bitmap に描画します。
  • Bitmap は黒のアルファマスクに変換してから PDF へ描画します。

PdfDocumentCanvas.drawText() だけでは、複雑な絵文字が複数の glyph に分離することがあります。EmojiSpan を利用すると、EmojiCompat が解決した1つの見た目を使用できます。

# 最小サンプル

このコードは、EmojiCompat の初期化完了後に呼び出します。androidx.emoji2:emoji2androidx.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;
    }
}

# 注意点

  • この方法では、絵文字は PDF 上で画像として扱われます。選択やコピーは維持されません。
  • 変換後の Bitmap は必要に応じて透明余白をトリミングします。
  • 同じ絵文字を繰り返し描画する場合は、文字列と文字サイズをキーにして Bitmap をキャッシュします。
  • EmojiSpan を得られない場合だけ、白黒絵文字フォントやシステム絵文字へフォールバックします。

この方法は、複雑な絵文字を1つの見た目として PDF に出力したい場合の実用的な選択肢です。