乐趣区

java-poi设置生成的word的图片为上下型环绕以及其位置

问题描述

在使用 poi-tl word 模版工具时,发现生成的文档中,图片格式为嵌入型,有的图片甚至被表格遮挡一半。而自己想要的图片格式为上下型环绕,并且图片需要居中。

问题分析

  1. poi-tl 渲染图片,使用的是 org.apache.poi.xwpf.usermodel.XWPFRun 的 addPicture 方法,该方法中有一段代码:CTInline inline = drawing.addNewInline();意思就是默认将图片转为 inline 类型,即行内元素。
  2. 然后我们把生成的嵌入型图片的文档转换成 xml 文件,然后再新建一个文档,插入图片后,设置图片为上下型环绕,保存为另一个 xml,比较下两个 xml 的区别。嵌入型图片的 xml 是:
    上下型环绕的图片的 xml 是

    我们看到两种格式的图片标签分别为 inline 和 anchor。所以如果我们想把图片设置为上下型环绕,需要重写 poi 的 addPicture 方法,把图片转为 anchor 类型。

  3. 我们仿照 org.apache.poi.xwpf.usermodel.XWPFRun 的 addPicture 方法,将 CTInline inline = drawing.addNewInline(); 换成 CTAnchor anchor = drawing.addNewAnchor();,然后对比着 xml,依次对 anchor 的字段进行赋值。结果发现生成的 word 无法正常打开,查了很多资料,都说 poi 的 CTAnchor 有问题,使用后无法正常打开生成的 word。
  4. 此路不通,那我们就尝试另一种思路,我们不通过 CTAnchor 来生成 anchor 标签,而是直接使用 xml,将 xml 赋给 poi 的 drawing。具体的处理方式在后面。

xml 标签和图片格式解析

在 word 中,在图片上右键,选择大小和位置,就可以看到如下界面:

图中的上下型对应的是 xml 中的 <wp:wrapTopAndBottom/> 标签,不同环绕方式该标签值不一样。如果需要其他格式,可以设置好后,把文档保存为 xml,找到对应的标签。
图中的距正文上下左右距离,对应的是 <wp:anchor distT=”71755″ distB=”71755″ distL=”114300″ distR=”114300″ …> 中的 disT、disB、disL、disR 属性。

图中位置一栏,水平对齐方式居中、相对于栏对应的是 xml 中的 <wp:positionH relativeFrom=”column”><wp:align>center</wp:align></wp:positionH>。
垂直 - 绝对位置 0.1cm,下侧段落对应的是 xml 中的 <wp:positionV relativeFrom=”paragraph”><wp:posOffset>36195</wp:posOffset></wp:positionV>。
我们可以根据不同的需要来设置不同的 xml。
我使用的 xml 是

 String xml = "<wp:anchor allowOverlap=\"0\"layoutInCell=\"1\"locked=\"0\"behindDoc=\"0\"relativeHeight=\"0\"simplePos=\"0\"distR=\"0\"distL=\"0\"distB=\"0\"distT=\"0\"" +" xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\"" +
                "xmlns:wp14=\"http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing\""+" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"  >"+"<wp:simplePos y=\"0\" x=\"0\"/>"+"<wp:positionH relativeFrom=\"column\">"+"<wp:align>center</wp:align>"+"</wp:positionH>"+"<wp:positionV relativeFrom=\"paragraph\">"+"<wp:posOffset>0</wp:posOffset>"+"</wp:positionV>"+"<wp:extent cy=\""+height+"\"cx=\""+width+"\"/>"+"<wp:effectExtent b=\"0\" r=\"0\" t=\"0\" l=\"0\"/>"+"<wp:wrapTopAndBottom/>"+"<wp:docPr descr=\"Picture Alt\" name=\"Picture Hit\" id=\"0\"/>"+"<wp:cNvGraphicFramePr>"+"<a:graphicFrameLocks noChangeAspect=\"true\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" />"+"</wp:cNvGraphicFramePr>"+"<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"+"<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"+"<pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"+"<pic:nvPicPr>"+"<pic:cNvPr name=\"Picture Hit\" id=\"1\"/>"+"<pic:cNvPicPr/>"+"</pic:nvPicPr>"+"<pic:blipFill>"+"<a:blip r:embed=\""+relationId+"\"/>" +
                "<a:stretch>" +
                "<a:fillRect/>" +
                "</a:stretch>" +
                "</pic:blipFill>" +
                "<pic:spPr>" +
                "<a:xfrm>" +
                "<a:off y=\"0\"x=\"0\"/>" +
                "<a:ext cy=\""+height+"\" cx=\""+width+"\"/>" +
                "</a:xfrm>" +
                "<a:prstGeom prst=\"rect\">" +
                "<a:avLst/>" +
                "</a:prstGeom>" +
                "</pic:spPr>" +
                "</pic:pic>" +
                "</a:graphicData>" +
                "</a:graphic>" +
                "<wp14:sizeRelH relativeFrom=\"margin\">" +
                "<wp14:pctWidth>0</wp14:pctWidth>" +
                "</wp14:sizeRelH>" +
                "<wp14:sizeRelV relativeFrom=\"margin\">" +
                "<wp14:pctHeight>0</wp14:pctHeight>" +
                "</wp14:sizeRelV>" +
                "</wp:anchor>";
                

其中 width 和 height 是图片的宽度和高度,relationId 是图片的 id。

解决方案

1,首先定义一个 poi-tl 的图片渲染器,使得其不再调用 poi 默认的图片渲染器,而是使用我们自己定义的。

public class MyPictureRenderPolicy extends AbstractRenderPolicy<PictureRenderData> {
@Override
protected boolean validate(PictureRenderData data) {return (null != data.getData() || null != data.getPath());
}

@Override
public void doRender(RunTemplate runTemplate, PictureRenderData picture, XWPFTemplate template)
        throws Exception {XWPFRun run = runTemplate.getRun();
    MyPictureRenderPolicy.Helper.renderPicture(run, picture);
}

@Override
protected void afterRender(RenderContext context) {clearPlaceholder(context, false);
}

@Override
protected void doRenderException(RunTemplate runTemplate, PictureRenderData data, Exception e) {logger.info("Render picture" + runTemplate + "error: {}", e.getMessage());
    runTemplate.getRun().setText(data.getAltMeta(), 0);
}

public static class Helper {public static void renderPicture(XWPFRun run, PictureRenderData picture) throws Exception {int suggestFileType = suggestFileType(picture.getPath());
        InputStream ins = null == picture.getData() ? new FileInputStream(picture.getPath())
                : new ByteArrayInputStream(picture.getData());

        String relationId = run.getDocument().addPictureData(ins, suggestFileType);
        long width = Units.toEMU(picture.getWidth());
        long height = Units.toEMU(picture.getHeight());
        CTDrawing drawing = run.getCTR().addNewDrawing();
        String xml = "<wp:anchor allowOverlap=\"0\"layoutInCell=\"1\"locked=\"0\"behindDoc=\"0\"relativeHeight=\"0\"simplePos=\"0\"distR=\"0\"distL=\"0\"distB=\"0\"distT=\"0\"" +" xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\"" +
                "xmlns:wp14=\"http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing\""+" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"  >"+"<wp:simplePos y=\"0\" x=\"0\"/>"+"<wp:positionH relativeFrom=\"column\">"+"<wp:align>center</wp:align>"+"</wp:positionH>"+"<wp:positionV relativeFrom=\"paragraph\">"+"<wp:posOffset>0</wp:posOffset>"+"</wp:positionV>"+"<wp:extent cy=\""+height+"\"cx=\""+width+"\"/>"+"<wp:effectExtent b=\"0\" r=\"0\" t=\"0\" l=\"0\"/>"+"<wp:wrapTopAndBottom/>"+"<wp:docPr descr=\"Picture Alt\" name=\"Picture Hit\" id=\"0\"/>"+"<wp:cNvGraphicFramePr>"+"<a:graphicFrameLocks noChangeAspect=\"true\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" />"+"</wp:cNvGraphicFramePr>"+"<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"+"<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"+"<pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"+"<pic:nvPicPr>"+"<pic:cNvPr name=\"Picture Hit\" id=\"1\"/>"+"<pic:cNvPicPr/>"+"</pic:nvPicPr>"+"<pic:blipFill>"+"<a:blip r:embed=\""+relationId+"\"/>" +
                "<a:stretch>" +
                "<a:fillRect/>" +
                "</a:stretch>" +
                "</pic:blipFill>" +
                "<pic:spPr>" +
                "<a:xfrm>" +
                "<a:off y=\"0\"x=\"0\"/>" +
                "<a:ext cy=\""+height+"\" cx=\""+width+"\"/>" +
                "</a:xfrm>" +
                "<a:prstGeom prst=\"rect\">" +
                "<a:avLst/>" +
                "</a:prstGeom>" +
                "</pic:spPr>" +
                "</pic:pic>" +
                "</a:graphicData>" +
                "</a:graphic>" +
                "<wp14:sizeRelH relativeFrom=\"margin\">" +
                "<wp14:pctWidth>0</wp14:pctWidth>" +
                "</wp14:sizeRelH>" +
                "<wp14:sizeRelV relativeFrom=\"margin\">" +
                "<wp14:pctHeight>0</wp14:pctHeight>" +
                "</wp14:sizeRelV>" +
                "</wp:anchor>";
        drawing.set(XmlToken.Factory.parse(xml, DEFAULT_XML_OPTIONS));
        CTPicture pic = getCTPictures(drawing).get(0);
        XWPFPicture xwpfPicture = new XWPFPicture(pic, run);
        run.getEmbeddedPictures().add(xwpfPicture);
    }


    public static List<CTPicture> getCTPictures(XmlObject o) {List<CTPicture> pictures = new ArrayList<>();
        XmlObject[] picts = o.selectPath("declare namespace pic='"
                + CTPicture.type.getName().getNamespaceURI() + "'.//pic:pic");
        for (XmlObject pict : picts) {if (pict instanceof XmlAnyTypeImpl) {
                // Pesky XmlBeans bug - see Bugzilla #49934
                try {pict = CTPicture.Factory.parse(pict.toString(),
                            DEFAULT_XML_OPTIONS);
                } catch (XmlException e) {throw new POIXMLException(e);
                }
            }
            if (pict instanceof CTPicture) {pictures.add((CTPicture) pict);
            }
        }
        return pictures;
    }


    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;
    }

}
}

然后在渲染模板的时候,配置我们自己定义的图片渲染器

 public static void main(String[] args) throws Exception{

    String path = "1.docx";
    InputStream templateFile = Demo.class.getClassLoader().getResourceAsStream(path);
    Map map = new HashMap();
    map.put("pic", new PictureRenderData(120, 80, ".png", Demo.class.getClassLoader().getResourceAsStream("1.png")));


    // 将数据整合到模板中去
    Configure.ConfigureBuilder builder = Configure.newBuilder();
    builder.supportGrammerRegexForAll();
    builder.addPlugin('@', new MyPictureRenderPolicy());
    XWPFTemplate template = XWPFTemplate.compile(templateFile, builder.build()).render(map);

    String docPath = "C:\\Users\\csdc01\\Desktop\\out.docx";
    FileOutputStream outputStream1 = new FileOutputStream(docPath);
    template.write(outputStream1);
    outputStream1.flush();
    outputStream1.close();}

源码:https://github.com/ksyzz/poi-…

退出移动版