具体需求为,使用 Apache POI 编辑 Word(docx) 文档,在其中插入一幅图片,将图片自动缩放至宽度恰好占满页面,图片环绕方式为上下或嵌入。使用 POI 在 Word 文档中插入图片,一般可以先创建一个 XWPFParagraph,在其下创建一个 XWPFRun,再将图片加入到 XWPFRun 中。其中,在 XWPFRun 中加入图片使用的 addPicture 方法需提供图片数据、图片类型、文件名称、图片宽度、图片高度,共五个参数。这里需要构造图片类型、图片宽度、图片高度,其中图片宽度和图片高度需要根据页芯高宽和图片原始高宽计算而成。
首先是程序主体,即获取图片类型、获取图片缩放后的尺寸,然后将其插入到文档段落中去,具体代码如下:
/**
* 执行操作。
*
* @param file 文件名称。
*/
public void perform(String file) throws IOException, InvalidFormatException {
// 获取图片类型。
PictureType type = getPictureType(file);
// 获取图片尺寸。
int[] size = getPictureSize(file);
int width = size[0], height = size[1];
// 文档插入图片。
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.addPicture(new FileInputStream(file), type, file, width, height);
}
其中,获取图片类型比较简单,写个分支转换就可以了,只支持 Word 自身支持的图片类型即可,具体代码如下:
/**
* 获取图片类型。
*
* @param file 图片文件。
* @return 图片类型。
*/
private PictureType getPictureType(String file) {
String type = file.substring(file.lastIndexOf(".") + 1);
if (type.equals("emf"))
return PictureType.EMF;
else if (type.equals("wmf"))
return PictureType.WMF;
else if (type.equals("pict"))
return PictureType.PICT;
else if (type.equals("jpg") || type.equals("jpeg"))
return PictureType.JPEG;
else if (type.equals("png"))
return PictureType.PNG;
else if (type.equals("dib"))
return PictureType.DIB;
else if (type.equals("gif"))
return PictureType.GIF;
else if (type.equals("tif"))
return PictureType.TIFF;
else if (type.equals("eps"))
return PictureType.EPS;
else if (type.equals("bmp"))
return PictureType.BMP;
else if (type.equals("wpg"))
return PictureType.WPG;
else if (type.equals("wdp"))
return PictureType.WDP;
else if (type.equals("svg"))
return PictureType.SVG;
else
return PictureType.UNKNOWN;
}
然后是计算图片缩放后的尺寸:这里我们可以通过 BufferedImage 类获取图片自身的尺寸,单位是像素点(point);通过 POI 的CT方法获得版芯尺寸,单位是缇(dxa);通过版芯尺寸的宽高比和图片自身的宽高比的大小关系,确定图片是宽度充满版芯,还是高度充满版芯;在计算图片在插入到文档时的尺寸,单位是EMU(English Metric Unit)。
这里计算图片尺寸比较容易,得到的结果单位是缇。麻烦的是要把缇转换为EMU。POI 的官方文档说可以使用 Units 类来处理这个转换,Units 没有直接的方法将缇转换为 EMU,需要将缇先转为点,再用 Units.toEMU 方法转为 EMU。还好缇的定义就是像素点的20分之一。这样这段计算图片缩放的代码就可以这样写了:
/**
* 获取图片尺寸。
*
* @param file 图片文件。
* @return 图片尺寸。
* @throws IOException 输入输出异常。
*/
private int[] getPictureSize(String file) throws IOException {
// 获取版心尺寸。
CTSectPr sectPr = document.getDocument().getBody().getSectPr();
CTPageSz pageSize = sectPr.getPgSz();
CTPageMar pageMar = sectPr.getPgMar();
int areaWidth = ((BigInteger) pageSize.getW()).intValue() - ((BigInteger) pageMar.getLeft()).intValue() - ((BigInteger) pageMar.getRight()).intValue();
int areaHeight = ((BigInteger) pageSize.getH()).intValue() - ((BigInteger) pageMar.getTop()).intValue() - ((BigInteger) pageMar.getBottom()).intValue();
// 计算图片缩放。
BufferedImage picture = ImageIO.read(new File(file));
int width = 0, height = 0;
if ((double) areaWidth / (double) picture.getWidth() < (double) areaHeight / (double) picture.getHeight()) {
width = areaWidth;
height = areaWidth * picture.getHeight() / picture.getWidth();
} else {
height = areaHeight;
width = areaHeight * picture.getWidth() / picture.getHeight();
}
// 计量单位转换。
return new int[]{Units.toEMU(width / 20), Units.toEMU(height / 20)};
}
最后再说说这几个单位:点和缇都是以像素描述的单位,具体大小与显示环境相关,1点=20缇;英寸和厘米都是物理长度单位,具体大小与显示环境无关,1英寸=2.54厘米。Word 在处理文档时,使用72dpi的分辨率,也就是1英寸=72点=1440缇。此时用公制的厘米与缇换算就会出小数了。因此引入了 EMU 这个单位,1英寸=914400EMU,1厘米=360000EMU,1点=12700EMU,1缇=635EMU。这样换算起来就都是整数了。
完整测试代码如下:
package example.apache_poi;
import org.apache.poi.common.usermodel.PictureType;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
/**
* 插入图片。
*/
public class InsertImage {
/**
* Dotx 文档。
*/
private XWPFDocument document;
/**
* 初始化插入图片。
*
* @param file 输入文件。
*/
public InsertImage(String file) throws IOException {
FileInputStream stream = new FileInputStream(file);
document = new XWPFDocument(stream);
}
/**
* 执行操作。
*
* @param file 文件名称。
*/
public void perform(String file) throws IOException, InvalidFormatException {
// 获取图片类型。
PictureType type = getPictureType(file);
// 获取图片尺寸。
int[] size = getPictureSize(file);
int width = size[0], height = size[1];
// 文档插入图片。
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.addPicture(new FileInputStream(file), type, file, width, height);
}
/**
* 获取图片类型。
*
* @param file 图片文件。
* @return 图片类型。
*/
private PictureType getPictureType(String file) {
String type = file.substring(file.lastIndexOf(".") + 1);
if (type.equals("emf"))
return PictureType.EMF;
else if (type.equals("wmf"))
return PictureType.WMF;
else if (type.equals("pict"))
return PictureType.PICT;
else if (type.equals("jpg") || type.equals("jpeg"))
return PictureType.JPEG;
else if (type.equals("png"))
return PictureType.PNG;
else if (type.equals("dib"))
return PictureType.DIB;
else if (type.equals("gif"))
return PictureType.GIF;
else if (type.equals("tif"))
return PictureType.TIFF;
else if (type.equals("eps"))
return PictureType.EPS;
else if (type.equals("bmp"))
return PictureType.BMP;
else if (type.equals("wpg"))
return PictureType.WPG;
else if (type.equals("wdp"))
return PictureType.WDP;
else if (type.equals("svg"))
return PictureType.SVG;
else
return PictureType.UNKNOWN;
}
/**
* 获取图片尺寸。
*
* @param file 图片文件。
* @return 图片尺寸。
* @throws IOException 输入输出异常。
*/
private int[] getPictureSize(String file) throws IOException {
// 获取版心尺寸。
CTSectPr sectPr = document.getDocument().getBody().getSectPr();
CTPageSz pageSize = sectPr.getPgSz();
CTPageMar pageMar = sectPr.getPgMar();
int areaWidth = ((BigInteger) pageSize.getW()).intValue() - ((BigInteger) pageMar.getLeft()).intValue() - ((BigInteger) pageMar.getRight()).intValue();
int areaHeight = ((BigInteger) pageSize.getH()).intValue() - ((BigInteger) pageMar.getTop()).intValue() - ((BigInteger) pageMar.getBottom()).intValue();
// 计算图片缩放。
BufferedImage picture = ImageIO.read(new File(file));
int width = 0, height = 0;
if ((double) areaWidth / (double) picture.getWidth() < (double) areaHeight / (double) picture.getHeight()) {
width = areaWidth;
height = areaWidth * picture.getHeight() / picture.getWidth();
} else {
height = areaHeight;
width = areaHeight * picture.getWidth() / picture.getHeight();
}
// 计量单位转换。
return new int[]{Units.toEMU(width) / 20, Units.toEMU(height) / 20};
}
/**
* 输出文档,
*
* @param file 输出文件。
* @throws IOException 输入输出异常。
*/
private void output(String file) throws IOException {
FileOutputStream stream = new FileOutputStream(file);
document.write(stream);
document.close();
stream.close();
}
/**
* 示范用法。
*/
public static void showhow() {
try {
InsertImage insert = new InsertImage("E:\\Archive\\Desktop\\Base.docx");
insert.perform("E:\\Archive\\Desktop\\Test.jpeg");
insert.output("E:\\Archive\\Desktop\\Test.docx");
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
}
运行环境:
- OpenJDK 17.0.0.1
- Apache POI 5.4.1
参考文献:

浙公网安备 33010602011771号