最近项目中使用了word合成,使用了poi-tl 进行处理。但是遇到要渲染一个图片,就一言难尽了。
官网默认的图片渲染结果会占住word的空间大小,导致整体间距会被拉大。
迫不得已,只能实现一个图片渲染的插件来实现图片浮动效果。
1.渲染类
CustomPictureRenderData 是对原poi-tl图片渲染类的扩展,添加了左偏移和上偏移属性。
这两属性用于图片位置的调整。
public class CustomPictureRenderData implements RenderData {
/**
* 图片宽度
*/
private int width;
/**
* 图片高度
*/
private int height;
/**
* 图片路径
*/
private String path;
/**
* 图片二进制数据
*/
private transient byte[] data;
/**
* 当图片不存在时,显示的文字
*/
private String altMeta = "";
/**
* 左偏量
*/
private int leftOffset;
/**
* 上偏移量
*/
private int topOffset;
// 省略get/set/构造等方法
}
2. 自定义渲染策略
PoiPicPolicy定义了具体的渲染方式,在poi-tl的默认渲染方式基础上添加了对样式的处理。
public class PoiPicPolicy extends AbstractRenderPolicy<CustomPictureRenderData> {
@Override
protected boolean validate(CustomPictureRenderData data) {
return (null != data && (null != data.getData() || null != data.getPath()));
}
@Override
protected void afterRender(RenderContext<CustomPictureRenderData> context) {
clearPlaceholder(context, false);
}
@Override
protected void reThrowException(RenderContext<CustomPictureRenderData> context, Exception e) {
logger.info("Render picture {}, error: {}", context.getEleTemplate(),e.getMessage());
context.getRun().setText(context.getData().getAltMeta(), 0);
}
@Override
public void doRender(RenderContext<CustomPictureRenderData> context) throws Exception {
Helper.renderPicture(context.getRun(), context.getData());
}
public static class Helper {
public static final int EMU = 9525;
public static void renderPicture(XWPFRun run, CustomPictureRenderData picture) throws Exception {
int suggestFileType = suggestFileType(picture.getPath());
InputStream ins = null;
try {
ins = null == picture.getData() ? new FileInputStream(picture.getPath())
: new ByteArrayInputStream(picture.getData());
String numberCode = RandomUtil.createNumberCode(4);
run.addPicture(ins, suggestFileType, "Generated"+numberCode, picture.getWidth() * EMU,
picture.getHeight() * EMU);
CTDrawing drawing = run.getCTR().getDrawingArray(0);
CTGraphicalObject graphicalobject = drawing.getInlineArray(0).getGraphic();
//拿到新插入的图片替换添加CTAnchor 设置浮动属性 删除inline属性
CTAnchor anchor = getAnchorWithGraphic(graphicalobject,
Units.toEMU(picture.getWidth()), Units.toEMU(picture.getHeight()),
Units.toEMU(picture.getLeftOffset()), Units.toEMU(picture.getTopOffset()));
//添加浮动属性
drawing.setAnchorArray(new CTAnchor[]{anchor});
//删除行内属性
drawing.removeInline(0);
}
finally {
IOUtils.closeQuietly(ins);
}
}
/**
* @param ctGraphicalObject 图片数据
* @param width 宽
* @param height 高
* @param leftOffset 水平偏移 left
* @param topOffset 垂直偏移 top
* @return
* @throws Exception
*/
private static CTAnchor getAnchorWithGraphic(CTGraphicalObject ctGraphicalObject, int width, int height,
int leftOffset, int topOffset) {
String anchorXML =
"<wp:anchor xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" "
+ "simplePos=\"0\" relativeHeight=\"0\" behindDoc=\"0\" locked=\"0\" layoutInCell=\"1\" allowOverlap=\"1\">"
+ "<wp:simplePos x=\"0\" y=\"0\"/>"
+ "<wp:positionH relativeFrom=\"column\">"
+ "<wp:posOffset>" + leftOffset + "</wp:posOffset>"
+ "</wp:positionH>"
+ "<wp:positionV relativeFrom=\"paragraph\">"
+ "<wp:posOffset>" + topOffset + "</wp:posOffset>" +
"</wp:positionV>"
+ "<wp:extent cx=\"" + width + "\" cy=\"" + height + "\"/>"
+ "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>"
+ "<wp:wrapNone/>"
+ "<wp:docPr id=\"1\" name=\"Drawing 0\" descr=\"G:/11.png\"/><wp:cNvGraphicFramePr/>"
+ "</wp:anchor>";
CTDrawing drawing = null;
try {
drawing = CTDrawing.Factory.parse(anchorXML);
} catch (XmlException e) {
e.printStackTrace();
}
CTAnchor anchor = drawing.getAnchorArray(0);
anchor.setGraphic(ctGraphicalObject);
return anchor;
}
public static int suggestFileType(String imgFile) {
int format = 0;
if (imgFile.endsWith(".emf")) format = XWPFDocument.PICTURE_TYPE_EMF;
else if (imgFile.endsWith(".wmf")) format = XWPFDocument.PICTURE_TYPE_WMF;
else if (imgFile.endsWith(".pict")) format = XWPFDocument.PICTURE_TYPE_PICT;
else if (imgFile.endsWith(".jpeg") || imgFile.endsWith(".jpg"))
format = XWPFDocument.PICTURE_TYPE_JPEG;
else if (imgFile.endsWith(".png")) format = XWPFDocument.PICTURE_TYPE_PNG;
else if (imgFile.endsWith(".dib")) format = XWPFDocument.PICTURE_TYPE_DIB;
else if (imgFile.endsWith(".gif")) format = XWPFDocument.PICTURE_TYPE_GIF;
else if (imgFile.endsWith(".tiff")) format = XWPFDocument.PICTURE_TYPE_TIFF;
else if (imgFile.endsWith(".eps")) format = XWPFDocument.PICTURE_TYPE_EPS;
else if (imgFile.endsWith(".bmp")) format = XWPFDocument.PICTURE_TYPE_BMP;
else if (imgFile.endsWith(".wpg")) format = XWPFDocument.PICTURE_TYPE_WPG;
else {
throw new RenderException("Unsupported picture: " + imgFile
+ ". Expected emf|wmf|pict|jpeg|png|dib|gif|tiff|eps|bmp|wpg");
}
return format;
}
}
}
3. 装配插件
将自定义策略以插件形式,加入默认配置中。
这里使用%,实际使用中替换类似{{%var}}的格式,当然也可以采用其他的标示符。
public class WordUtil {
/**
* 添加自定义配置
*/
public static final Configure config = Configure.createDefault()
.plugin('%', new PoiPicPolicy());
public static void replaceWord(OutputStream response, InputStream in, Map<String, Object> params) throws Exception {
XWPFTemplate template = null;
try {
template = XWPFTemplate.compile(in,config).render(params);
template.write(response);
}finally {
if (template != null) {
template.close();
}
}
}
}