ビジネス報告や自動化システムにおいて、生成された 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 解析を用いた別アプローチの検討も必須となります。