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是:clipboard.png
    上下型環繞的圖片的xml是clipboard.png

    我們看到兩種格式的圖片標籤分別爲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中,在圖片上右鍵,選擇大小和位置,就可以看到如下界面:
clipboard.png
圖中的上下型對應的是xml中的<wp:wrapTopAndBottom/>標籤,不同環繞方式該標籤值不一樣。如果需要其他格式,可以設置好後,把文檔保存爲xml,找到對應的標籤。
圖中的距正文上下左右距離,對應的是<wp:anchor distT="71755" distB="71755" distL="114300" distR="114300" ...>中的disT、disB、disL、disR屬性。
clipboard.png
圖中位置一欄,水平對齊方式居中、相對於欄對應的是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-...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章