Java で Word 文書に画像を埋め込むための実装手法とライブラリ選定

ビジネス報告や自動化システムにおいて、生成された Word 文書(.docx)に画像を表示させる必要があるケースは頻繁にあります。外部ファイルへのリンクではなく、画像データをドキュメント内部に格納することで、ファイルの移動や転送先環境に関わらず、始终一貫して画像が見える状態を維持することが重要です。本稿では、この要件を満たすための代表的な実装アプローチとコード例を解説します。

Apache POI を利用した標準的な実装

最も広く採用されている方法は、Apache POI の XWPF モジュールを利用することです。ZIP 形式である .docx ファイル内部構造に対して直接アクセスし、メディアセクションに画像データを書き込む仕組みを提供しています。

Maven 設定

<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-full</artifactId>
        <version>5.2.4</version>
    </dependency>
</dependencies>

実装サンプル

以下のクラス定義では、入力ストリームからバイナリーデータを取得し、段落内の Run オブジェクトへ直接組み込みます。try-with-resources を使用することでリソース管理を確実に行います。

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.XWPFPictureData;
import java.io.*;
import java.util.List;

public class POIDocumentGenerator {

    public static void createDocWithEmbed(String inputImagePath, String outputFilePath) throws Exception {
        
        // ドキュメント新規作成
        XWPFDocument doc = new XWPFDocument();

        // テキストと画像を含む段落を作成
        XWPFParagraph para = doc.createParagraph();
        XWPFRun run = para.createRun();
        run.setText("本文書のタイトル:");
        run.setBold(true);
        run.addBreak();

        // 画像データの読み取り
        try (FileInputStream fis = new FileInputStream(inputImagePath)) {
            byte[] imgBytes = fis.readAllBytes();
            
            // リストに追加し ID を割り当て(ポイライブラリの仕様に従う場合)
            List<XWPFPictureData> pictures = doc.getAllPictures();
            int idx = pictures.size() + 1; 

            // Run に画像を追加
            run.addPicture(new ByteArrayInputStream(imgBytes), 
                           XWPFPictureData.PICTURE_TYPE_JPEG, 
                           "embedded.jpg", 
                           Units.toEMU(400), 
                           Units.toEMU(300));
        }

        // 出力用ストリームで保存
        try (FileOutputStream fos = new FileOutputStream(outputFilePath)) {
            doc.write(fos);
        } finally {
            doc.close();
        }
    }
}

テンプレートベースのアプローチ (Freemarker)

複雑なレイアウトを持つ既存の文書をベースにした自動生成が必要な場合、テンプレートエンジンの活用が有効です。この手法では、Word の XML 構造そのものを操作する形になります。

手順としては、元となる .docx 拡張子のファイルを解凍(ZIP エディタ等)、Content.xml を編集し、変数プレースホルダーを設定します。その後、Java からテンプレートをレンダリングし、再度圧縮して生成します。

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.*;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Base64;

public class TemplateBasedExporter {

    public static void exportViaTemplate(String templatePath, String imagePath, String outPath) throws Exception {
        
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_33);
        cfg.setDefaultEncoding("UTF-8");
        
        // ビジネスロジックとして画像を Base64 ストリング化
        Path imgPath = Paths.get(imagePath);
        byte[] rawBytes = Files.readAllBytes(imgPath);
        String b64Img = Base64.getEncoder().encodeToString(rawBytes);

        Map data = new HashMap<>();
        data.put("docTitle", "自動生成レポート");
        data.put("imgContent", b64Img);

        // テンプレートロード(実際の .xml コンテンツを想定)
        cfg.setDirectoryForTemplateLoading(new File(templatePath));
        Template temp = cfg.getTemplate("report_template.ftl");

        FileWriter writer = new FileWriter(outPath);
        temp.process(data, writer);
        writer.flush();
        writer.close();
        
        // 必要に応じてファイル名を .docx に変更するか、Zip 処理を適用
    }
}

Docx4j による低レベル制御

Office Open XML の完全なサポートを必要とする場合に適しています。XML ツリー構造を Java オブジェクトモデルで直接構築するため、POI よりも詳細なカスタマイズが可能ですが、API が複雑になる傾向があります。

import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.PartNameImpl;
import org.docx4j.wml.ObjectFactory;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.docx4j.wml.Drawing;
import org.docx4j.wml.Inline;
import javax.xml.bind.JAXBElement;

public class AdvancedDocBuilder {

    public static void buildComplexDoc(String imgUrl, String saveUrl) throws Exception {
        WordprocessingMLPackage pkg = WordprocessingMLPackage.createPackage();
        
        // プールからの Factory インスタンス取得
        ObjectFactory factory = new ObjectFactory();

        // パラグラフとラン構成
        P p = factory.createP();
        R r = factory.createR();
        
        // アイテムリストへのテキスト追加
        JAXBElement<Text> t1 = factory.createText("文書開始。");
        r.getContent().add(t1);

        // 画像パーツの追加
        byte[] imgBin = java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(imgUrl));
        BinaryPartAbstractImage binaryPart = 
            BinaryPartAbstractImage.createImagePart(pkg, imgBin);

        Inline inline = binaryPart.createImageInline("photo.jpg", "説明文", 1, 1, true);
        Drawing drawing = factory.createDrawing(inline);
        r.getContent().add(factory.createDrawing(inline));
        
        // ドキュメントツリーへ展開
        p.getContent().add(r);
        pkg.getMainDocumentPart().getContent().add(p);

        // バイナリ保存
        pkg.save(new java.io.File(saveUrl));
    }
}

技術選定の比較評価

技術栈学習難易度柔軟性推奨シナリオ
Apache POI中級者高い一般的なレポート生成、軽量な埋め込み
Freemarker上級者非常に高いデザイン変更頻度高い業務フロー
Docx4j高度最大限コンプライアンス対応など厳密な XML 制御

標準的な機能性と開発工数のバランスを考慮すると、Apache POI の addPicture メカニズムを採用するケースが大半を占めます。ただし、既存のデザインテンプレートを引き継ぐ場合は、コンテンツ Zip 解析を用いた別アプローチの検討も必須となります。

タグ: Apache POI Freemarker Docx4j Office Open XML Java Development

6月30日 21:42 投稿