Java实现word文档替换,以及插入图片

Java实现word文档替换,以及在指定插入图片
在开发过程中 我们可能会遇到这样的需求,给你一个word文档,可能是合同等其他,需要你把文档内容补充完整 ,把字段赋值进去或者在指定位置插入一张图片

如果有需要完成品的工具类直接拉倒最后,开箱即用

教学开始

pom.xml

这里采用的是apache poi 的包 开源免费放心食用

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>ooxml-schemas</artifactId>
            <version>1.4</version>
        </dependency>

下面开始实操给大家解决

文档字段替换

前后效果图对比
替换前:

替换前
替换后:

替换后
名字成功变成PG_L

附上代码实例

   
    public static void main(String[] args) {
        //这个map用于存储要替换的字段
        Map<String, Object> params = new HashMap<>();
        params.put("name", "PG_L");
        //模板的路径
        String wordUrl = "D:\\TuTu\\测试.docx";
        //要存储的文件的路径
        String newWordUrl = "D:\\TuTu\\测试2.docx";
        InputStream is = null;
        try {
            //读取文件
            is = new FileInputStream(wordUrl);
            XWPFDocument doc = new XWPFDocument(is);
            //替换段落里面的变量
            POIUtil.replaceInPara(doc, params);
            //输出到测试2文件中
            OutputStream os = new FileOutputStream(newWordUrl);
            doc.write(os);
            is.close();
            os.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

// POIUtil工具类
    /**
     * 替换段落里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    public static void replaceInPara(XWPFDocument doc, Map<String, Object> params) {
        for(XWPFParagraph para : doc.getParagraphs()) {
            replaceInPara(para, params);
        }
    }
    /**
     * 替换段落里面的变量
     *
     * @param paragraph   要替换的段落
     * @param params 参数
     */
    private static void replaceInPara(XWPFParagraph paragraph, Map<String, Object> params) {
        List<XWPFRun> runs = paragraph.getRuns();
        for (int i = 0; i < runs.size(); i++) {
            //获取字符
            String text0 = runs.get(i).getText(runs.get(i).getTextPosition());
            if (text0 != null && text0.contains("$")) {
                //包含占位符的字符缓存
                StringBuilder cache = new StringBuilder(text0);
                int endIndex = 0;
                boolean contains = text0.contains("}");
                //同一个run中是否包含占位符
                if (!contains) {
                    int j = i + 1;
                    for (; j < runs.size(); j++) {
                        String text1 = runs.get(j).getText(runs.get(j).getTextPosition());
                        if (text1 == null) {
                            continue;
                        }
                        cache.append(text1);
                        if (text1.contains("}")) {
                            endIndex = j;
                            break;
                        }
                    }
                }
                if (contains || endIndex != 0) {
                    //处理替换
                    String s = cache.toString();
                    for (Map.Entry<String, Object> entry : params.entrySet()) {
                        String k = entry.getKey();
                        String v = entry.getValue().toString();
                        if (s.contains("${" + k +"}")) {
                            String replace = s.replace("${" + k +"}", v);
                            runs.get(i).setText(replace, 0);
                            for (int j = endIndex; j > i; j--) {
                                runs.get(j).setText("", 0);
                            }


                            break;
                        }
                    }
                }
            }
        }
    }


有小伙伴可能要问,屁股刘(PG_L),那我想给替换的字段指定样式怎么办?当…当然可以了,但没必要,我的建议是在创建模板的时候就预定义好字段样式属性。如果实在有这方面的诉求,我后期可以单独开一篇文章来讲。

插入图片

插入图片我的建议是用书签来做
打开你的word文档,选择插入,选择书签,并命名
书签
替换后的图片:
替换后的图片
代码实例:

    public static void main(String[] args) {
        Map<String, Object> params = new HashMap<>();
        params.put("name", "PG_L");

        String wordUrl = "D:\\TuTu\\测试.docx";
        String newWordUrl = "D:\\TuTu\\测试2.docx";
        InputStream is = null;
        try {
            InputStream picStream = new FileInputStream("D:\\TuTu\\tutu.jpg");
            params.put("mypic", picStream);
            is = new FileInputStream(wordUrl);
            XWPFDocument doc = new XWPFDocument(is);
            //替换段落里面的变量
            POIUtil.replaceInPara(doc, params);
            OutputStream os = new FileOutputStream(newWordUrl);
            doc.write(os);
            is.close();
            os.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//POIUtil
    /**
     * 替换段落里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    public static void replaceInPara(XWPFDocument doc, Map<String, Object> params) {
        for(XWPFParagraph para : doc.getParagraphs()) {
            replaceInBook(para, params);
        }
    }
        /**
     * 替换段落里面的书签
     *
     * @param paragraph   要替换的段落
     * @param params 参数
     */
    private static void replaceInBook(XWPFParagraph paragraph, Map<String, Object> params) {
        CTP ctp = paragraph.getCTP();
        for (int dwI = 0; dwI < ctp.sizeOfBookmarkStartArray(); dwI++) {
            try {
                CTBookmark bookmark = ctp.getBookmarkStartArray(dwI);
                if(!params.containsKey(bookmark.getName())) continue;
                InputStream ins = (InputStream) params.get(bookmark.getName());
                XWPFRun run = paragraph.createRun();
                //bus.png为鼠标在word里选择图片时,图片显示的名字,100,100则为像素单元,根据实际需要的大小进行调整即可。
                run.addPicture(ins,XWPFDocument.PICTURE_TYPE_PNG,"tutu.png,", Units.toEMU(100), Units.toEMU(100));
            } catch (InvalidFormatException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

替换表格里的字段

前面讲了怎么替换字段 但是那是段落里的字段,表格里的其实也类似 无非多了一段步骤:找到表格、找到行、找到单元格、再找到段落(替换表格里的图片也类似操作,不再赘述)下面看案例:
替换前:
替换前
替换后:
替换后

    public static void main(String[] args) {
        Map<String, Object> params = new HashMap<>();
        params.put("name", "PG_L");
        params.put("henshuai","比坤坤都要帅");

        String wordUrl = "D:\\TuTu\\测试.docx";
        String newWordUrl = "D:\\TuTu\\测试2.docx";
        InputStream is = null;
        try {
            InputStream picStream = new FileInputStream("D:\\TuTu\\tutu.jpg");
            params.put("mypic", picStream);
            is = new FileInputStream(wordUrl);
            XWPFDocument doc = new XWPFDocument(is);
            //替换段落里面的变量
            POIUtil.replaceInTable(doc, params);
            OutputStream os = new FileOutputStream(newWordUrl);
            doc.write(os);
            is.close();
            os.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//POIUtil
    /**
     * 替换表格里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    private static void replaceInTable(XWPFDocument doc, Map<String, Object> params) {
        for(XWPFTable table : doc.getTables()){
            replaceInTable(table, params);
        }
    }
    /**
     * 替换表格里面的变量
     * @param table 要替换的表格
     * @param params 参数
     */
    private static void replaceInTable(XWPFTable table, Map<String, Object> params) {
        for(XWPFTableRow row : table.getRows()){
            for(XWPFTableCell cell : row.getTableCells()){
                for(XWPFTable secTable : cell.getTables()){
                    replaceInTable(secTable,params);
                }
                for (XWPFParagraph para : cell.getParagraphs()){
                    replaceInPara(para, params);
                    replaceInBook(para, params);
                }
            }
        }
    }

    /**
     * 替换段落里面的变量  这个和之前的一样 用就完事了
     *
     * @param paragraph   要替换的段落
     * @param params 参数
     */
    private static void replaceInPara(XWPFParagraph paragraph, Map<String, Object> params) {
        List<XWPFRun> runs = paragraph.getRuns();
        for (int i = 0; i < runs.size(); i++) {
            //获取字符
            String text0 = runs.get(i).getText(runs.get(i).getTextPosition());
            if (text0 != null && text0.contains("$")) {
                //包含占位符的字符缓存
                StringBuilder cache = new StringBuilder(text0);
                int endIndex = 0;
                boolean contains = text0.contains("}");
                //同一个run中是否包含占位符
                if (!contains) {
                    int j = i + 1;
                    for (; j < runs.size(); j++) {
                        String text1 = runs.get(j).getText(runs.get(j).getTextPosition());
                        if (text1 == null) {
                            continue;
                        }
                        cache.append(text1);
                        if (text1.contains("}")) {
                            endIndex = j;
                            break;
                        }
                    }
                }
                if (contains || endIndex != 0) {
                    //处理替换
                    String s = cache.toString();
                    for (Map.Entry<String, Object> entry : params.entrySet()) {
                        String k = entry.getKey();
                        String v = entry.getValue().toString();
                        if (s.contains("${" + k +"}")) {
                            String replace = s.replace("${" + k +"}", v);
                            runs.get(i).setText(replace, 0);
                            for (int j = endIndex; j > i; j--) {
                                runs.get(j).setText("", 0);
                            }


                            break;
                        }
                    }
                }
            }
        }
    }

图片悬浮

正常插入的图片会影响插入位置行的样式 ,但是如果设置了图片悬浮,就可以完美解决,不过需要自定义好图片位置,设置偏移量
在我屁股刘(PG_L)经过n轮尝试后 发现 重写apache poi下的包实现起来最最简单拉

先在自己的项目里 建一个包
org.apache.poi.xwpf.usermodel
然后把源码的XWPFRun拷贝到包里,然加上我的方法搞定

这里代码就直接展示具体插入图片的方法

    //原方法
    run.addPicture(ins,XWPFDocument.PICTURE_TYPE_PNG,"bus.png,", Units.toEMU(100), Units.toEMU(100));
    



//新方法
    /**
     *
     *  复制原本的方法,加入了是否悬浮图片;在加入图片的偏移位置参数
     *  x和y的取值:1厘米=360045
     * @param pictureData
     * @param pictureType
     * @param filename
     * @param width
     * @param height
     * @param isXuanfu
     * @param x  水平偏移位置
     * @param y  垂直偏移位置
     * @return
     * @throws InvalidFormatException
     * @throws IOException
     */
    public XWPFPicture addPictureV2(InputStream pictureData, int pictureType, String filename, int width, int height,boolean isXuanfu,int x,int y)
            throws InvalidFormatException, IOException {
        String relationId;
        XWPFPictureData picData;

        // Work out what to add the picture to, then add both the
        //  picture and the relationship for it
        // TODO Should we have an interface for this sort of thing?
        if (parent.getPart() instanceof XWPFHeaderFooter) {
            XWPFHeaderFooter headerFooter = (XWPFHeaderFooter) parent.getPart();
            relationId = headerFooter.addPictureData(pictureData, pictureType);
            picData = (XWPFPictureData) headerFooter.getRelationById(relationId);
        } else {
            @SuppressWarnings("resource")
            XWPFDocument doc = parent.getDocument();
            relationId = doc.addPictureData(pictureData, pictureType);
            picData = (XWPFPictureData) doc.getRelationById(relationId);
        }

        // Create the drawing entry for it
        try {
            CTDrawing drawing = run.addNewDrawing();
//            CTInline inline = drawing.addNewInline();

            //*************修改 zhengmeng
            CTAnchor inline = drawing.addNewAnchor();
            // Do the fiddly namespace bits on the inline
            // (We need full control of what goes where and as what)
            String xml =
                    "<a:graphic xmlns:a=\"" + CTGraphicalObject.type.getName().getNamespaceURI() + "\">" +
                            "<a:graphicData uri=\"" + CTPicture.type.getName().getNamespaceURI() + "\">" +
                            "<pic:pic xmlns:pic=\"" + CTPicture.type.getName().getNamespaceURI() + "\" />" +
                            "</a:graphicData>" +
                            "</a:graphic>";
            InputSource is = new InputSource(new StringReader(xml));
            org.w3c.dom.Document doc = DocumentHelper.readDocument(is);
            inline.set(XmlToken.Factory.parse(doc.getDocumentElement(), DEFAULT_XML_OPTIONS));

            // Setup the inline
            inline.setDistT(0);
            inline.setDistR(0);
            inline.setDistB(0);
            inline.setDistL(0);

            //*********************修改zhengmeng**********************************
            inline.setSimplePos2(false);
            inline.setRelativeHeight(251658240);//查了文档 ,也试过,没找到作用位置,先放着
            inline.setBehindDoc(isXuanfu);//重点,浮于文字上方
            inline.setLocked(false);
            inline.setLayoutInCell(true);
            inline.setAllowOverlap(true);

            CTPoint2D simplePost = inline.addNewSimplePos();
            simplePost.setX(0);
            simplePost.setY(0);
            CTPosH ph = inline.addNewPositionH();
            ph.setRelativeFrom(STRelFromH.PAGE);//以整个页面为准的定位
            ph.setPosOffset(x);//水平位置,针对RelativeFrom的一个偏移 1厘米=360045 ,1.1*360045
            CTPosV pv = inline.addNewPositionV();
            pv.setRelativeFrom(STRelFromV.PAGE);
            pv.setPosOffset(y);//竖直位置,针对RelativeFrom的一个偏移 1.3*360045

            /**
             * 这个是此元素指定的其他程度应添加到每个边缘 (顶部、 底部、 左、 右)
             * 以补偿应用于 DrawingML 对象的任何图形效果的图像
             * 但目前好像用不到
             */
            CTEffectExtent efextent = inline.addNewEffectExtent();
            efextent.setL(19050);
            efextent.setT(0);
            efextent.setR(0);
            efextent.setB(0);

            CTWrapNone wn = inline.addNewWrapNone();

            CTNonVisualGraphicFrameProperties fp = inline.addNewCNvGraphicFramePr();
            //*******************************************************


            CTNonVisualDrawingProps docPr = inline.addNewDocPr();
            long id = getParent().getDocument().getDrawingIdManager().reserveNew();
            docPr.setId(id);
            /* This name is not visible in Word 2010 anywhere. */
            docPr.setName("Drawing " + id);
            docPr.setDescr(filename);

            CTPositiveSize2D extent = inline.addNewExtent();
            extent.setCx(width);
            extent.setCy(height);

            // Grab the picture object
            CTGraphicalObject graphic = inline.getGraphic();
            CTGraphicalObjectData graphicData = graphic.getGraphicData();
            CTPicture pic = getCTPictures(graphicData).get(0);

            // Set it up
            CTPictureNonVisual nvPicPr = pic.addNewNvPicPr();

            CTNonVisualDrawingProps cNvPr = nvPicPr.addNewCNvPr();
            /* use "0" for the id. See ECM-576, 20.2.2.3 */
            cNvPr.setId(0L);
            /* This name is not visible in Word 2010 anywhere */
            cNvPr.setName("Picture " + id);
            cNvPr.setDescr(filename);

            CTNonVisualPictureProperties cNvPicPr = nvPicPr.addNewCNvPicPr();
            cNvPicPr.addNewPicLocks().setNoChangeAspect(true);

            CTBlipFillProperties blipFill = pic.addNewBlipFill();
            CTBlip blip = blipFill.addNewBlip();
            blip.setEmbed(parent.getPart().getRelationId(picData));

            /********   增加  zhengmeng***********/
            blip.setCstate(STBlipCompression.PRINT);//压缩状态

            blipFill.addNewStretch().addNewFillRect();

            CTShapeProperties spPr = pic.addNewSpPr();
            CTTransform2D xfrm = spPr.addNewXfrm();

            CTPoint2D off = xfrm.addNewOff();
            off.setX(0);
            off.setY(0);

            CTPositiveSize2D ext = xfrm.addNewExt();
            ext.setCx(width);
            ext.setCy(height);

            CTPresetGeometry2D prstGeom = spPr.addNewPrstGeom();
            prstGeom.setPrst(STShapeType.RECT);
            prstGeom.addNewAvLst();

            // Finish up
            XWPFPicture xwpfPicture = new XWPFPicture(pic, this);
            pictures.add(xwpfPicture);
            return xwpfPicture;
        } catch (XmlException | SAXException e) {
            throw new IllegalStateException(e);
        }
    }

这样基本的word替换就实现完了 接下来附上工具类

完整的POIUtil工具类


public class POIUtil {

    /**
     * 用一个docx文档作为模板,然后替换其中的内容,再写入目标文档中。
     * @throws Exception
     */
    public static ByteArrayOutputStream templateWrite(String filePath/*, String outFilePath*/, Map<String, Object> params) throws Exception {
        InputStream is = new FileInputStream(filePath);
        XWPFDocument doc = new XWPFDocument(is);
        //替换段落里面的变量
        replaceInPara(doc, params);
        //替换表格里面的变量
        replaceInTable(doc, params);
        //OutputStream os = new FileOutputStream(outFilePath);

        ByteArrayOutputStream os = new ByteArrayOutputStream();//二进制OutputStream
        doc.write(os);
        close(os);
        close(is);
        return os;
    }

    /**
     * 替换段落里面的书签
     *
     * @param paragraph   要替换的段落
     * @param params 参数
     */
    private static void replaceInBook(XWPFParagraph paragraph, Map<String, Object> params) {
        if(params == null){
            return;
        }
        CTP ctp = paragraph.getCTP();
        for (int dwI = 0; dwI < ctp.sizeOfBookmarkStartArray(); dwI++) {
            try {
                CTBookmark bookmark = ctp.getBookmarkStartArray(dwI);
                if(!params.containsKey(bookmark.getName())) continue;
                PicIuputStreamModel model = (PicIuputStreamModel) params.get(bookmark.getName());
                InputStream picIs = model.getIs();
                if(picIs == null) continue;
                XWPFRun run = paragraph.createRun();
                //bus.png为鼠标在word里选择图片时,图片显示的名字,400,400则为像素单元,根据实际需要的大小进行调整即可。
                run.addPictureV2(picIs,XWPFDocument.PICTURE_TYPE_PNG,"bus.png,", Units.toEMU(model.getWidth()), Units.toEMU(model.getHeight()),true,(int)(model.getLeftOffset()*360045),(int)(model.getTopOffset()*360045));
            } catch (InvalidFormatException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 替换段落里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    private static void replaceInPara(XWPFDocument doc, Map<String, Object> params) {
        for(XWPFParagraph para : doc.getParagraphs()) {
            replaceInPara(para, params);
            replaceInBook(para, params);
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param paragraph   要替换的段落
     * @param params 参数
     */
    private static void replaceInPara(XWPFParagraph paragraph, Map<String, Object> params) {
        if(params == null){
            return;
        }
        List<XWPFRun> runs = paragraph.getRuns();
        for (int i = 0; i < runs.size(); i++) {
            //获取字符
            String text0 = runs.get(i).getText(runs.get(i).getTextPosition());
            if (text0 != null && text0.contains("$")) {
                //包含占位符的字符缓存
                StringBuilder cache = new StringBuilder(text0);
                int endIndex = 0;
                boolean contains = text0.contains("}");
                //同一个run中是否包含占位符
                if (!contains) {
                    int j = i + 1;
                    for (; j < runs.size(); j++) {
                        String text1 = runs.get(j).getText(runs.get(j).getTextPosition());
                        if (text1 == null) {
                            continue;
                        }
                        cache.append(text1);
                        if (text1.contains("}")) {
                            endIndex = j;
                            break;
                        }
                    }
                }
                if (contains || endIndex != 0) {
                    //处理替换
                    String s = cache.toString();
                    //System.out.println("替换字符:"+s);
                    List<String> keys = get$Value(s);
                    for (String key : keys) {
                        String value = "";
                        if(params.containsKey(key)){
                            value = params.get(key).toString();
                        }
                        String replace = s.replace("${" + key +"}", value);
                        runs.get(i).setText(replace, 0);
                        for (int j = endIndex; j > i; j--) {
                            runs.get(j).setText("", 0);
                        }
                    }

//                    for (Map.Entry<String, Object> entry : params.entrySet()) {
//                        String k = entry.getKey();
//                        String v = entry.getValue().toString();
//                        if (s.contains("${" + k +"}")) {
//                            String replace = s.replace("${" + k +"}", v);
//                            runs.get(i).setText(replace, 0);
//                            for (int j = endIndex; j > i; j--) {
//                                runs.get(j).setText("", 0);
//                            }
//
//
//                            break;
//                        }
//                    }
                }
            }
        }
    }


    /**
     * 替换表格里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    private static void replaceInTable(XWPFDocument doc, Map<String, Object> params) {
        for(XWPFTable table : doc.getTables()){
            replaceInTable(table, params);
        }
    }
    /**
     * 替换表格里面的变量
     * @param table 要替换的表格
     * @param params 参数
     */
    private static void replaceInTable(XWPFTable table, Map<String, Object> params) {
        for(XWPFTableRow row : table.getRows()){
            for(XWPFTableCell cell : row.getTableCells()){
                for(XWPFTable secTable : cell.getTables()){
                    replaceInTable(secTable,params);
                }
                for (XWPFParagraph para : cell.getParagraphs()){
                    replaceInPara(para, params);
                    replaceInBook(para, params);
                }
            }
        }
    }

    public static List<String> get$Value(String str) {
        List<String> variables = new ArrayList<>();
        Pattern pattern = Pattern.compile("\\$\\{([^}]+)}");
        Matcher matcher = pattern.matcher(str);
        while (matcher.find()) {
            variables.add(matcher.group(1));
        }
        return variables;
    }

    /**
     * 关闭输入流
     * @param is
     */
    private static void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     * @param os
     */
    private static void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }




}

重写后的 XWPFRun

public class XWPFRun implements ISDTContents, IRunElement, CharacterRun {
    private CTR run;
    private String pictureText;
    private IRunBody parent;
    private List<XWPFPicture> pictures;

    /**
     * @param r the CTR bean which holds the run attributes
     * @param p the parent paragraph
     */
    public XWPFRun(CTR r, IRunBody p) {
        this.run = r;
        this.parent = p;

        /*
         * reserve already occupied drawing ids, so reserving new ids later will
         * not corrupt the document
         */
        for (CTDrawing ctDrawing : r.getDrawingArray()) {
            for (CTAnchor anchor : ctDrawing.getAnchorArray()) {
                if (anchor.getDocPr() != null) {
                    getDocument().getDrawingIdManager().reserve(anchor.getDocPr().getId());
                }
            }
            for (CTInline inline : ctDrawing.getInlineArray()) {
                if (inline.getDocPr() != null) {
                    getDocument().getDrawingIdManager().reserve(inline.getDocPr().getId());
                }
            }
        }

        // Look for any text in any of our pictures or drawings
        StringBuilder text = new StringBuilder();
        List<XmlObject> pictTextObjs = new ArrayList<>();
        pictTextObjs.addAll(Arrays.asList(r.getPictArray()));
        pictTextObjs.addAll(Arrays.asList(r.getDrawingArray()));
        for (XmlObject o : pictTextObjs) {
            XmlObject[] ts = o.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//w:t");
            for (XmlObject t : ts) {
                NodeList kids = t.getDomNode().getChildNodes();
                for (int n = 0; n < kids.getLength(); n++) {
                    if (kids.item(n) instanceof Text) {
                        if (text.length() > 0) {
                            text.append("\n");
                        }
                        text.append(kids.item(n).getNodeValue());
                    }
                }
            }
        }
        pictureText = text.toString();

        // Do we have any embedded pictures?
        // (They're a different CTPicture, under the drawingml namespace)
        pictures = new ArrayList<>();
        for (XmlObject o : pictTextObjs) {
            for (CTPicture pict : getCTPictures(o)) {
                XWPFPicture picture = new XWPFPicture(pict, this);
                pictures.add(picture);
            }
        }
    }

    /**
     * @deprecated Use {@link XWPFRun#XWPFRun(CTR, IRunBody)}
     */
    @Deprecated
    public XWPFRun(CTR r, XWPFParagraph p) {
        this(r, (IRunBody) p);
    }

    /**
     * Add the xml:spaces="preserve" attribute if the string has leading or trailing white spaces
     *
     * @param xs the string to check
     */
    static void preserveSpaces(XmlString xs) {
        String text = xs.getStringValue();
        if (text != null && (text.startsWith(" ") || text.endsWith(" "))) {
            XmlCursor c = xs.newCursor();
            c.toNextToken();
            c.insertAttributeWithValue(new QName("http://www.w3.org/XML/1998/namespace", "space"), "preserve");
            c.dispose();
        }
    }

    private List<CTPicture> getCTPictures(XmlObject o) {
        List<CTPicture> pics = 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) {
                pics.add((CTPicture) pict);
            }
        }
        return pics;
    }

    /**
     * Get the currently used CTR object
     *
     * @return ctr object
     */
    @Internal
    public CTR getCTR() {
        return run;
    }

    /**
     * Get the currently referenced paragraph/SDT object
     *
     * @return current parent
     */
    public IRunBody getParent() {
        return parent;
    }

    /**
     * Get the currently referenced paragraph, or null if a SDT object
     *
     * @deprecated use {@link XWPFRun#getParent()} instead
     */
    @Deprecated
    public XWPFParagraph getParagraph() {
        if (parent instanceof XWPFParagraph) {
            return (XWPFParagraph) parent;
        }
        return null;
    }

    /**
     * @return The {@link XWPFDocument} instance, this run belongs to, or
     * <code>null</code> if parent structure (paragraph > document) is not properly set.
     */
    public XWPFDocument getDocument() {
        if (parent != null) {
            return parent.getDocument();
        }
        return null;
    }

    /**
     * For isBold, isItalic etc
     */
    private static boolean isCTOnOff(CTOnOff onoff) {
        if (!onoff.isSetVal()) {
            return true;
        }
        final STOnOff.Enum val = onoff.getVal();
        return (
                (STOnOff.TRUE == val) ||
                        (STOnOff.X_1 == val) ||
                        (STOnOff.ON == val)
        );
    }

    /**
     * Get the language tag associated with this run, if any.
     *
     * @return the language tag associated with this run, if any
     */
    public String getLang() {
        CTRPr pr = getRunProperties(false);
        Object lang = pr == null || !pr.isSetLang() ? null : pr.getLang().getVal();
        return (String) lang;
    }

    /**
     * Set the language tag associated with this run.
     *
     * @param lang the language tag associated with this run
     * @since 4.1.0
     */
    public void setLang(String lang) {
        CTRPr pr = getRunProperties(true);
        CTLanguage ctLang = pr.isSetLang() ? pr.getLang() : pr.addNewLang();
        ctLang.setVal(lang);
    }

    /**
     * Whether the bold property shall be applied to all non-complex script
     * characters in the contents of this run when displayed in a document
     *
     * @return <code>true</code> if the bold property is applied
     */
    @Override
    public boolean isBold() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetB() && isCTOnOff(pr.getB());
    }

    /**
     * Whether the bold property shall be applied to all non-complex script
     * characters in the contents of this run when displayed in a document.
     * <p>
     * This formatting property is a toggle property, which specifies that its
     * behavior differs between its use within a style definition and its use as
     * direct formatting. When used as part of a style definition, setting this
     * property shall toggle the current state of that property as specified up
     * to this point in the hierarchy (i.e. applied to not applied, and vice
     * versa). Setting it to <code>false</code> (or an equivalent) shall
     * result in the current setting remaining unchanged. However, when used as
     * direct formatting, setting this property to true or false shall set the
     * absolute state of the resulting property.
     * </p>
     * <p>
     * If this element is not present, the default value is to leave the
     * formatting applied at previous level in the style hierarchy. If this
     * element is never applied in the style hierarchy, then bold shall not be
     * applied to non-complex script characters.
     * </p>
     *
     * @param value <code>true</code> if the bold property is applied to
     *              this run
     */
    @Override
    public void setBold(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff bold = pr.isSetB() ? pr.getB() : pr.addNewB();
        bold.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    /**
     * Get text color. The returned value is a string in the hex form "RRGGBB".
     */
    public String getColor() {
        String color = null;
        if (run.isSetRPr()) {
            CTRPr pr = getRunProperties(false);
            if (pr != null && pr.isSetColor()) {
                CTColor clr = pr.getColor();
                color = clr.xgetVal().getStringValue();
            }
        }
        return color;
    }

    /**
     * Set text color.
     *
     * @param rgbStr - the desired color, in the hex form "RRGGBB".
     */
    public void setColor(String rgbStr) {
        CTRPr pr = getRunProperties(true);
        CTColor color = pr.isSetColor() ? pr.getColor() : pr.addNewColor();
        color.setVal(rgbStr);
    }

    /**
     * Return the string content of this text run
     *
     * @return the text of this text run or <code>null</code> if not set
     */
    public String getText(int pos) {
        return run.sizeOfTArray() == 0 ? null : run.getTArray(pos)
                .getStringValue();
    }

    /**
     * Returns text embedded in pictures
     */
    public String getPictureText() {
        return pictureText;
    }

    /**
     * Sets the text of this text run
     *
     * @param value the literal text which shall be displayed in the document
     */
    public void setText(String value) {
        setText(value, run.sizeOfTArray());
    }

    /**
     * Sets the text of this text run in the
     *
     * @param value the literal text which shall be displayed in the document
     * @param pos   - position in the text array (NB: 0 based)
     */
    public void setText(String value, int pos) {
        if (pos > run.sizeOfTArray()) {
            throw new ArrayIndexOutOfBoundsException("Value too large for the parameter position in XWPFRun.setText(String value,int pos)");
        }
        CTText t = (pos < run.sizeOfTArray() && pos >= 0) ? run.getTArray(pos) : run.addNewT();
        t.setStringValue(value);
        preserveSpaces(t);
    }

    /**
     * Whether the italic property should be applied to all non-complex script
     * characters in the contents of this run when displayed in a document.
     *
     * @return <code>true</code> if the italic property is applied
     */
    @Override
    public boolean isItalic() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetI() && isCTOnOff(pr.getI());
    }

    /**
     * Whether the bold property shall be applied to all non-complex script
     * characters in the contents of this run when displayed in a document
     * <p>
     * <p>
     * This formatting property is a toggle property, which specifies that its
     * behavior differs between its use within a style definition and its use as
     * direct formatting. When used as part of a style definition, setting this
     * property shall toggle the current state of that property as specified up
     * to this point in the hierarchy (i.e. applied to not applied, and vice
     * versa). Setting it to <code>false</code> (or an equivalent) shall
     * result in the current setting remaining unchanged. However, when used as
     * direct formatting, setting this property to true or false shall set the
     * absolute state of the resulting property.
     * </p>
     * <p>
     * If this element is not present, the default value is to leave the
     * formatting applied at previous level in the style hierarchy. If this
     * element is never applied in the style hierarchy, then bold shall not be
     * applied to non-complex script characters.
     * </p>
     *
     * @param value <code>true</code> if the italic property is applied to
     *              this run
     */
    @Override
    public void setItalic(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff italic = pr.isSetI() ? pr.getI() : pr.addNewI();
        italic.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    /**
     * Get the underline setting for the run.
     *
     * @return the Underline pattern applied to this run
     * @see (@link UnderlinePatterns}
     */
    public UnderlinePatterns getUnderline() {
        UnderlinePatterns value = UnderlinePatterns.NONE;
        CTUnderline underline = getCTUnderline(false);
        if (underline != null) {
            STUnderline.Enum baseValue = underline.getVal();
            if (baseValue != null) {
                value = UnderlinePatterns.valueOf(baseValue.intValue());
            }
        }
        return value;
    }

    /**
     * Specifies that the contents of this run should be displayed along with an
     * underline appearing directly below the character height.
     * <p>
     * If this element is not present, the default value is to leave the
     * formatting applied at previous level in the style hierarchy. If this
     * element is never applied in the style hierarchy, then an underline shall
     * not be applied to the contents of this run.
     * </p>
     *
     * @param value -
     *              underline type
     * @see {@link UnderlinePatterns} : all possible patterns that could be applied
     */
    public void setUnderline(UnderlinePatterns value) {
        CTUnderline underline = getCTUnderline(true);
        underline.setVal(STUnderline.Enum.forInt(value.getValue()));
    }

    /**
     * Get the CTUnderline for the run.
     * @param create Create a new underline if necessary
     * @return The underline, or null create is false and there is no underline.
     */
    private CTUnderline getCTUnderline(boolean create) {
        CTRPr pr = getRunProperties(true);
        CTUnderline underline = pr.getU();
        if (create && underline == null) {
            underline = pr.addNewU();
        }
        return underline;
    }

    /**
     * Set the underline color for the run's underline, if any.
     *
     * @param color An RGB color value (e.g, "a0C6F3") or "auto".
     * @since 4.0.0
     */
    public void setUnderlineColor(String color) {
        CTUnderline underline = getCTUnderline(true);
        SimpleValue svColor = null;
        if (color.equals("auto")) {
            STHexColorAuto hexColor = STHexColorAuto.Factory.newInstance();
            hexColor.set(STHexColorAuto.Enum.forString(color));
            svColor = (SimpleValue) hexColor;
        } else {
            STHexColorRGB rgbColor = STHexColorRGB.Factory.newInstance();
            rgbColor.setStringValue(color);
            svColor = (SimpleValue) rgbColor;
        }
        underline.setColor(svColor);
    }

    /**
     * Set the underline theme color for the run's underline, if any.
     *
     * @param themeColor A theme color name (see {@link STThemeColor.Enum}).
     * @since 4.0.0
     */
    public void setUnderlineThemeColor(String themeColor) {
        CTUnderline underline = getCTUnderline(true);
        STThemeColor.Enum val = STThemeColor.Enum.forString(themeColor);
        if (val != null) {
            underline.setThemeColor(val);
        }
    }

    /**
     * Get the underline theme color for the run's underline, if any.
     *
     * @return The {@link STThemeColor.Enum}.
     * @since 4.0.0
     */
    public STThemeColor.Enum getUnderlineThemeColor() {
        CTUnderline underline = getCTUnderline(false);
        STThemeColor.Enum color = STThemeColor.NONE;
        if (underline != null) {
            color = underline.getThemeColor();
        }
        return color;
    }

    /**
     * Get the underline color for the run's underline, if any.
     *
     * @return The RGB color value as as a string of hexadecimal digits (e.g., "A0B2F1") or "auto".
     * @since 4.0.0
     */
    public String getUnderlineColor() {
        CTUnderline underline = getCTUnderline(true);
        String colorName = "auto";
        Object rawValue = underline.getColor();
        if (rawValue != null) {
            if (rawValue instanceof String) {
                colorName = (String)rawValue;
            } else {
                byte[] rgbColor = (byte[])rawValue;
                colorName = HexDump.toHex(rgbColor[0]) + HexDump.toHex(rgbColor[1]) + HexDump.toHex(rgbColor[2]);
            }
        }
        return colorName;
    }

    /**
     * Specifies that the contents of this run shall be displayed with a single
     * horizontal line through the center of the line.
     *
     * @return <code>true</code> if the strike property is applied
     */
    @Override
    public boolean isStrikeThrough() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetStrike() && isCTOnOff(pr.getStrike());
    }

    /**
     * Specifies that the contents of this run shall be displayed with a single
     * horizontal line through the center of the line.
     * <p>
     * This formatting property is a toggle property, which specifies that its
     * behaviour differs between its use within a style definition and its use as
     * direct formatting. When used as part of a style definition, setting this
     * property shall toggle the current state of that property as specified up
     * to this point in the hierarchy (i.e. applied to not applied, and vice
     * versa). Setting it to false (or an equivalent) shall result in the
     * current setting remaining unchanged. However, when used as direct
     * formatting, setting this property to true or false shall set the absolute
     * state of the resulting property.
     * </p>
     * <p>
     * If this element is not present, the default value is to leave the
     * formatting applied at previous level in the style hierarchy. If this
     * element is never applied in the style hierarchy, then strikethrough shall
     * not be applied to the contents of this run.
     * </p>
     *
     * @param value <code>true</code> if the strike property is applied to
     *              this run
     */
    @Override
    public void setStrikeThrough(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff strike = pr.isSetStrike() ? pr.getStrike() : pr.addNewStrike();
        strike.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    @Deprecated
    public boolean isStrike() {
        return isStrikeThrough();
    }

    @Deprecated
    public void setStrike(boolean value) {
        setStrikeThrough(value);
    }

    /**
     * Specifies that the contents of this run shall be displayed with a double
     * horizontal line through the center of the line.
     *
     * @return <code>true</code> if the double strike property is applied
     */
    @Override
    public boolean isDoubleStrikeThrough() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetDstrike() && isCTOnOff(pr.getDstrike());
    }

    /**
     * Specifies that the contents of this run shall be displayed with a
     * double horizontal line through the center of the line.
     *
     * @see #setStrikeThrough(boolean) for the rules about this
     */
    @Override
    public void setDoubleStrikethrough(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff dstrike = pr.isSetDstrike() ? pr.getDstrike() : pr.addNewDstrike();
        dstrike.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    @Override
    public boolean isSmallCaps() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetSmallCaps() && isCTOnOff(pr.getSmallCaps());
    }

    @Override
    public void setSmallCaps(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff caps = pr.isSetSmallCaps() ? pr.getSmallCaps() : pr.addNewSmallCaps();
        caps.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    @Override
    public boolean isCapitalized() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetCaps() && isCTOnOff(pr.getCaps());
    }

    @Override
    public void setCapitalized(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff caps = pr.isSetCaps() ? pr.getCaps() : pr.addNewCaps();
        caps.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    @Override
    public boolean isShadowed() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetShadow() && isCTOnOff(pr.getShadow());
    }

    @Override
    public void setShadow(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff shadow = pr.isSetShadow() ? pr.getShadow() : pr.addNewShadow();
        shadow.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    @Override
    public boolean isImprinted() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetImprint() && isCTOnOff(pr.getImprint());
    }

    @Override
    public void setImprinted(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff imprinted = pr.isSetImprint() ? pr.getImprint() : pr.addNewImprint();
        imprinted.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    @Override
    public boolean isEmbossed() {
        CTRPr pr = getRunProperties(false);
        return pr != null && pr.isSetEmboss() && isCTOnOff(pr.getEmboss());
    }

    @Override
    public void setEmbossed(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff emboss = pr.isSetEmboss() ? pr.getEmboss() : pr.addNewEmboss();
        emboss.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    /**
     * Specifies the alignment which shall be applied to the contents of this
     * run in relation to the default appearance of the run's text.
     * This allows the text to be repositioned as subscript or superscript without
     * altering the font size of the run properties.
     *
     * @return VerticalAlign
     * @see {@link VerticalAlign} all possible value that could be applyed to this run
     * @deprecated use {@link XWPFRun.getVerticalAlignment}
     */
    @Removal(version = "4.2")
    public VerticalAlign getSubscript() {
        CTRPr pr = getRunProperties(false);
        return (pr != null && pr.isSetVertAlign()) ? VerticalAlign.valueOf(pr.getVertAlign().getVal().intValue()) : VerticalAlign.BASELINE;
    }

    /**
     * Specifies the alignment which shall be applied to the contents of this
     * run in relation to the default appearance of the run's text. This allows
     * the text to be repositioned as subscript or superscript without altering
     * the font size of the run properties.
     * <p>
     * If this element is not present, the default value is to leave the
     * formatting applied at previous level in the style hierarchy. If this
     * element is never applied in the style hierarchy, then the text shall not
     * be subscript or superscript relative to the default baseline location for
     * the contents of this run.
     * </p>
     *
     * @param valign Type of vertical align to apply
     * @see VerticalAlign
     */
    public void setSubscript(VerticalAlign valign) {
        CTRPr pr = getRunProperties(true);
        CTVerticalAlignRun ctValign = pr.isSetVertAlign() ? pr.getVertAlign() : pr.addNewVertAlign();
        ctValign.setVal(STVerticalAlignRun.Enum.forInt(valign.getValue()));
    }

    @Override
    public int getKerning() {
        CTRPr pr = getRunProperties(false);
        if (pr == null || !pr.isSetKern()) {
            return 0;
        }
        return pr.getKern().getVal().intValue();
    }

    @Override
    public void setKerning(int kern) {
        CTRPr pr = getRunProperties(true);
        CTHpsMeasure kernmes = pr.isSetKern() ? pr.getKern() : pr.addNewKern();
        kernmes.setVal(BigInteger.valueOf(kern));
    }

    @Override
    public boolean isHighlighted() {
        CTRPr pr = getRunProperties(false);
        if (pr == null || !pr.isSetHighlight()) {
            return false;
        }
        STHighlightColor.Enum val = pr.getHighlight().getVal();
        if (val == null || val == STHighlightColor.NONE) {
            return false;
        }
        return true;
    }
    // TODO Provide a wrapper round STHighlightColor, then expose getter/setter
    //  for the highlight colour. Ideally also then add to CharacterRun interface

    @Override
    public int getCharacterSpacing() {
        CTRPr pr = getRunProperties(false);
        if (pr == null || !pr.isSetSpacing()) {
            return 0;
        }
        return pr.getSpacing().getVal().intValue();
    }

    @Override
    public void setCharacterSpacing(int twips) {
        CTRPr pr = getRunProperties(true);
        CTSignedTwipsMeasure spc = pr.isSetSpacing() ? pr.getSpacing() : pr.addNewSpacing();
        spc.setVal(BigInteger.valueOf(twips));
    }

    /**
     * Gets the fonts which shall be used to display the text contents of
     * this run. Specifies a font which shall be used to format all characters
     * in the ASCII range (0 - 127) within the parent run
     *
     * @return a string representing the font family
     */
    public String getFontFamily() {
        return getFontFamily(null);
    }

    /**
     * Specifies the fonts which shall be used to display the text contents of
     * this run. Specifies a font which shall be used to format all characters
     * in the ASCII range (0 - 127) within the parent run.
     * <p>
     * Also sets the other font ranges, if they haven't been set before
     *
     * @param fontFamily The font family to apply
     * @see FontCharRange
     */
    public void setFontFamily(String fontFamily) {
        setFontFamily(fontFamily, null);
    }

    /**
     * Alias for {@link #getFontFamily()}
     */
    @Override
    public String getFontName() {
        return getFontFamily();
    }

    /**
     * Gets the font family for the specified font char range.
     * If fcr is null, the font char range "ascii" is used
     *
     * @param fcr the font char range, defaults to "ansi"
     * @return a string representing the font famil
     */
    public String getFontFamily(FontCharRange fcr) {
        CTRPr pr = getRunProperties(false);
        if (pr == null || !pr.isSetRFonts()) {
            return null;
        }

        CTFonts fonts = pr.getRFonts();
        switch (fcr == null ? FontCharRange.ascii : fcr) {
            default:
            case ascii:
                return fonts.getAscii();
            case cs:
                return fonts.getCs();
            case eastAsia:
                return fonts.getEastAsia();
            case hAnsi:
                return fonts.getHAnsi();
        }
    }

    /**
     * Specifies the fonts which shall be used to display the text contents of
     * this run. The default handling for fcr == null is to overwrite the
     * ascii font char range with the given font family and also set all not
     * specified font ranges
     *
     * @param fontFamily The font family to apply
     * @param fcr        FontCharRange or null for default handling
     */
    public void setFontFamily(String fontFamily, FontCharRange fcr) {
        CTRPr pr = getRunProperties(true);
        CTFonts fonts = pr.isSetRFonts() ? pr.getRFonts() : pr.addNewRFonts();

        if (fcr == null) {
            fonts.setAscii(fontFamily);
            if (!fonts.isSetHAnsi()) {
                fonts.setHAnsi(fontFamily);
            }
            if (!fonts.isSetCs()) {
                fonts.setCs(fontFamily);
            }
            if (!fonts.isSetEastAsia()) {
                fonts.setEastAsia(fontFamily);
            }
        } else {
            switch (fcr) {
                case ascii:
                    fonts.setAscii(fontFamily);
                    break;
                case cs:
                    fonts.setCs(fontFamily);
                    break;
                case eastAsia:
                    fonts.setEastAsia(fontFamily);
                    break;
                case hAnsi:
                    fonts.setHAnsi(fontFamily);
                    break;
            }
        }
    }

    /**
     * Specifies the font size which shall be applied to all non complex script
     * characters in the contents of this run when displayed.
     *
     * @return value representing the font size
     */
    @Override
    public int getFontSize() {
        CTRPr pr = getRunProperties(false);
        return (pr != null && pr.isSetSz()) ? pr.getSz().getVal().divide(new BigInteger("2")).intValue() : -1;
    }

    /**
     * Specifies the font size which shall be applied to all non complex script
     * characters in the contents of this run when displayed.
     * <p>
     * If this element is not present, the default value is to leave the value
     * applied at previous level in the style hierarchy. If this element is
     * never applied in the style hierarchy, then any appropriate font size may
     * be used for non complex script characters.
     * </p>
     *
     * @param size The font size as number of point measurements.
     */
    @Override
    public void setFontSize(int size) {
        BigInteger bint = new BigInteger(Integer.toString(size));
        CTRPr pr = getRunProperties(true);
        CTHpsMeasure ctSize = pr.isSetSz() ? pr.getSz() : pr.addNewSz();
        ctSize.setVal(bint.multiply(new BigInteger("2")));
    }

    /**
     * This element specifies the amount by which text shall be raised or
     * lowered for this run in relation to the default baseline of the
     * surrounding non-positioned text. This allows the text to be repositioned
     * without altering the font size of the contents.
     *
     * @return a big integer representing the amount of text shall be "moved"
     */
    public int getTextPosition() {
        CTRPr pr = getRunProperties(false);
        return (pr != null && pr.isSetPosition()) ? pr.getPosition().getVal().intValue()
                : -1;
    }

    /**
     * This element specifies the amount by which text shall be raised or
     * lowered for this run in relation to the default baseline of the
     * surrounding non-positioned text. This allows the text to be repositioned
     * without altering the font size of the contents.
     * <p>
     * If the val attribute is positive, then the parent run shall be raised
     * above the baseline of the surrounding text by the specified number of
     * half-points. If the val attribute is negative, then the parent run shall
     * be lowered below the baseline of the surrounding text by the specified
     * number of half-points.
     * </p>
     * <p>
     * If this element is not present, the default value is to leave the
     * formatting applied at previous level in the style hierarchy. If this
     * element is never applied in the style hierarchy, then the text shall not
     * be raised or lowered relative to the default baseline location for the
     * contents of this run.
     * </p>
     *
     * @param val Positive values will raise the baseline of the text, negative
     *            values will lower it.
     */
    public void setTextPosition(int val) {
        BigInteger bint = new BigInteger(Integer.toString(val));
        CTRPr pr = getRunProperties(true);
        CTSignedHpsMeasure position = pr.isSetPosition() ? pr.getPosition() : pr.addNewPosition();
        position.setVal(bint);
    }

    /**
     *
     */
    public void removeBreak() {
        // TODO
    }

    /**
     * Specifies that a break shall be placed at the current location in the run
     * content.
     * A break is a special character which is used to override the
     * normal line breaking that would be performed based on the normal layout
     * of the document's contents.
     *
     * @see #addCarriageReturn()
     */
    public void addBreak() {
        run.addNewBr();
    }

    /**
     * Specifies that a break shall be placed at the current location in the run
     * content.
     * A break is a special character which is used to override the
     * normal line breaking that would be performed based on the normal layout
     * of the document's contents.
     * <p>
     * The behavior of this break character (the
     * location where text shall be restarted after this break) shall be
     * determined by its type values.
     * </p>
     *
     * @see BreakType
     */
    public void addBreak(BreakType type) {
        CTBr br = run.addNewBr();
        br.setType(STBrType.Enum.forInt(type.getValue()));
    }

    /**
     * Specifies that a break shall be placed at the current location in the run
     * content. A break is a special character which is used to override the
     * normal line breaking that would be performed based on the normal layout
     * of the document's contents.
     * <p>
     * The behavior of this break character (the
     * location where text shall be restarted after this break) shall be
     * determined by its type (in this case is BreakType.TEXT_WRAPPING as default) and clear attribute values.
     * </p>
     *
     * @see BreakClear
     */
    public void addBreak(BreakClear clear) {
        CTBr br = run.addNewBr();
        br.setType(STBrType.Enum.forInt(BreakType.TEXT_WRAPPING.getValue()));
        br.setClear(STBrClear.Enum.forInt(clear.getValue()));
    }

    /**
     * Specifies that a tab shall be placed at the current location in
     * the run content.
     */
    public void addTab() {
        run.addNewTab();
    }

    public void removeTab() {
        //TODO
    }

    /**
     * Specifies that a carriage return shall be placed at the
     * current location in the run content.
     * A carriage return is used to end the current line of text in
     * Wordprocess.
     * The behavior of a carriage return in run content shall be
     * identical to a break character with null type and clear attributes, which
     * shall end the current line and find the next available line on which to
     * continue.
     * The carriage return character forced the following text to be
     * restarted on the next available line in the document.
     */
    public void addCarriageReturn() {
        run.addNewCr();
    }

    public void removeCarriageReturn() {
        //TODO
    }

    /**
     *
     *  Adds a picture to the run. This method handles
     *  attaching the picture data to the overall file.
     *  复制原本的方法,加入了是否悬浮图片;在加入图片的偏移位置参数
     *  x和y的取值:1厘米=360045
     * @param pictureData
     * @param pictureType
     * @param filename
     * @param width
     * @param height
     * @param isXuanfu
     * @param x  水平偏移位置
     * @param y  垂直偏移位置
     * @return
     * @throws InvalidFormatException
     * @throws IOException
     */
    public XWPFPicture addPictureV2(InputStream pictureData, int pictureType, String filename, int width, int height,boolean isXuanfu,int x,int y)
            throws InvalidFormatException, IOException {
        String relationId;
        XWPFPictureData picData;

        // Work out what to add the picture to, then add both the
        //  picture and the relationship for it
        // TODO Should we have an interface for this sort of thing?
        if (parent.getPart() instanceof XWPFHeaderFooter) {
            XWPFHeaderFooter headerFooter = (XWPFHeaderFooter) parent.getPart();
            relationId = headerFooter.addPictureData(pictureData, pictureType);
            picData = (XWPFPictureData) headerFooter.getRelationById(relationId);
        } else {
            @SuppressWarnings("resource")
            XWPFDocument doc = parent.getDocument();
            relationId = doc.addPictureData(pictureData, pictureType);
            picData = (XWPFPictureData) doc.getRelationById(relationId);
        }

        // Create the drawing entry for it
        try {
            CTDrawing drawing = run.addNewDrawing();
//            CTInline inline = drawing.addNewInline();

            //*************修改 zhengmeng
            CTAnchor inline = drawing.addNewAnchor();
            // Do the fiddly namespace bits on the inline
            // (We need full control of what goes where and as what)
            String xml =
                    "<a:graphic xmlns:a=\"" + CTGraphicalObject.type.getName().getNamespaceURI() + "\">" +
                            "<a:graphicData uri=\"" + CTPicture.type.getName().getNamespaceURI() + "\">" +
                            "<pic:pic xmlns:pic=\"" + CTPicture.type.getName().getNamespaceURI() + "\" />" +
                            "</a:graphicData>" +
                            "</a:graphic>";
            InputSource is = new InputSource(new StringReader(xml));
            org.w3c.dom.Document doc = DocumentHelper.readDocument(is);
            inline.set(XmlToken.Factory.parse(doc.getDocumentElement(), DEFAULT_XML_OPTIONS));

            // Setup the inline
            inline.setDistT(0);
            inline.setDistR(0);
            inline.setDistB(0);
            inline.setDistL(0);

            //*********************修改zhengmeng**********************************
            inline.setSimplePos2(false);
            inline.setRelativeHeight(251658240);//查了文档 ,也试过,没找到作用位置,先放着
            inline.setBehindDoc(isXuanfu);//重点,浮于文字上方
            inline.setLocked(false);
            inline.setLayoutInCell(true);
            inline.setAllowOverlap(true);

            CTPoint2D simplePost = inline.addNewSimplePos();
            simplePost.setX(0);
            simplePost.setY(0);
            CTPosH ph = inline.addNewPositionH();
            ph.setRelativeFrom(STRelFromH.PAGE);//以整个页面为准的定位
            ph.setPosOffset(x);//水平位置,针对RelativeFrom的一个偏移 1厘米=360045 ,1.1*360045
            CTPosV pv = inline.addNewPositionV();
            pv.setRelativeFrom(STRelFromV.PAGE);
            pv.setPosOffset(y);//竖直位置,针对RelativeFrom的一个偏移 1.3*360045

            /**
             * 这个是此元素指定的其他程度应添加到每个边缘 (顶部、 底部、 左、 右)
             * 以补偿应用于 DrawingML 对象的任何图形效果的图像
             * 但目前好像用不到
             */
            CTEffectExtent efextent = inline.addNewEffectExtent();
            efextent.setL(19050);
            efextent.setT(0);
            efextent.setR(0);
            efextent.setB(0);

            CTWrapNone wn = inline.addNewWrapNone();

            CTNonVisualGraphicFrameProperties fp = inline.addNewCNvGraphicFramePr();
            //*******************************************************


            CTNonVisualDrawingProps docPr = inline.addNewDocPr();
            long id = getParent().getDocument().getDrawingIdManager().reserveNew();
            docPr.setId(id);
            /* This name is not visible in Word 2010 anywhere. */
            docPr.setName("Drawing " + id);
            docPr.setDescr(filename);

            CTPositiveSize2D extent = inline.addNewExtent();
            extent.setCx(width);
            extent.setCy(height);

            // Grab the picture object
            CTGraphicalObject graphic = inline.getGraphic();
            CTGraphicalObjectData graphicData = graphic.getGraphicData();
            CTPicture pic = getCTPictures(graphicData).get(0);

            // Set it up
            CTPictureNonVisual nvPicPr = pic.addNewNvPicPr();

            CTNonVisualDrawingProps cNvPr = nvPicPr.addNewCNvPr();
            /* use "0" for the id. See ECM-576, 20.2.2.3 */
            cNvPr.setId(0L);
            /* This name is not visible in Word 2010 anywhere */
            cNvPr.setName("Picture " + id);
            cNvPr.setDescr(filename);

            CTNonVisualPictureProperties cNvPicPr = nvPicPr.addNewCNvPicPr();
            cNvPicPr.addNewPicLocks().setNoChangeAspect(true);

            CTBlipFillProperties blipFill = pic.addNewBlipFill();
            CTBlip blip = blipFill.addNewBlip();
            blip.setEmbed(parent.getPart().getRelationId(picData));

            /********   增加  zhengmeng***********/
            blip.setCstate(STBlipCompression.PRINT);//压缩状态

            blipFill.addNewStretch().addNewFillRect();

            CTShapeProperties spPr = pic.addNewSpPr();
            CTTransform2D xfrm = spPr.addNewXfrm();

            CTPoint2D off = xfrm.addNewOff();
            off.setX(0);
            off.setY(0);

            CTPositiveSize2D ext = xfrm.addNewExt();
            ext.setCx(width);
            ext.setCy(height);

            CTPresetGeometry2D prstGeom = spPr.addNewPrstGeom();
            prstGeom.setPrst(STShapeType.RECT);
            prstGeom.addNewAvLst();

            // Finish up
            XWPFPicture xwpfPicture = new XWPFPicture(pic, this);
            pictures.add(xwpfPicture);
            return xwpfPicture;
        } catch (XmlException | SAXException e) {
            throw new IllegalStateException(e);
        }
    }

    public XWPFPicture addPicture(InputStream pictureData, int pictureType, String filename, int width, int height) throws InvalidFormatException, IOException {
        String relationId;
        XWPFPictureData picData;
        if (this.parent.getPart() instanceof XWPFHeaderFooter) {
            XWPFHeaderFooter headerFooter = (XWPFHeaderFooter)this.parent.getPart();
            relationId = headerFooter.addPictureData(pictureData, pictureType);
            picData = (XWPFPictureData)headerFooter.getRelationById(relationId);
        } else {
            XWPFDocument doc = this.parent.getDocument();
            relationId = doc.addPictureData(pictureData, pictureType);
            picData = (XWPFPictureData)doc.getRelationById(relationId);
        }

        try {
            CTDrawing drawing = this.run.addNewDrawing();
            CTInline inline = drawing.addNewInline();
            String xml = "<a:graphic xmlns:a=\"" + CTGraphicalObject.type.getName().getNamespaceURI() + "\"><a:graphicData uri=\"" + CTPicture.type.getName().getNamespaceURI() + "\"><pic:pic xmlns:pic=\"" + CTPicture.type.getName().getNamespaceURI() + "\" /></a:graphicData></a:graphic>";
            InputSource is = new InputSource(new StringReader(xml));
            Document doc = DocumentHelper.readDocument(is);
            inline.set(org.apache.xmlbeans.XmlToken.Factory.parse(doc.getDocumentElement(), POIXMLTypeLoader.DEFAULT_XML_OPTIONS));
            inline.setDistT(0L);
            inline.setDistR(0L);
            inline.setDistB(0L);
            inline.setDistL(0L);
            CTNonVisualDrawingProps docPr = inline.addNewDocPr();
            long id = this.getParent().getDocument().getDrawingIdManager().reserveNew();
            docPr.setId(id);
            docPr.setName("Drawing " + id);
            docPr.setDescr(filename);
            CTPositiveSize2D extent = inline.addNewExtent();
            extent.setCx((long)width);
            extent.setCy((long)height);
            CTGraphicalObject graphic = inline.getGraphic();
            CTGraphicalObjectData graphicData = graphic.getGraphicData();
            CTPicture pic = (CTPicture)this.getCTPictures(graphicData).get(0);
            CTPictureNonVisual nvPicPr = pic.addNewNvPicPr();
            CTNonVisualDrawingProps cNvPr = nvPicPr.addNewCNvPr();
            cNvPr.setId(0L);
            cNvPr.setName("Picture " + id);
            cNvPr.setDescr(filename);
            CTNonVisualPictureProperties cNvPicPr = nvPicPr.addNewCNvPicPr();
            cNvPicPr.addNewPicLocks().setNoChangeAspect(true);
            CTBlipFillProperties blipFill = pic.addNewBlipFill();
            CTBlip blip = blipFill.addNewBlip();
            blip.setEmbed(this.parent.getPart().getRelationId(picData));
            blipFill.addNewStretch().addNewFillRect();
            CTShapeProperties spPr = pic.addNewSpPr();
            CTTransform2D xfrm = spPr.addNewXfrm();
            CTPoint2D off = xfrm.addNewOff();
            off.setX(0L);
            off.setY(0L);
            CTPositiveSize2D ext = xfrm.addNewExt();
            ext.setCx((long)width);
            ext.setCy((long)height);
            CTPresetGeometry2D prstGeom = spPr.addNewPrstGeom();
            prstGeom.setPrst(STShapeType.RECT);
            prstGeom.addNewAvLst();
            XWPFPicture xwpfPicture = new XWPFPicture(pic, this);
            this.pictures.add(xwpfPicture);
            return xwpfPicture;
        } catch (SAXException | XmlException var31) {
            throw new IllegalStateException(var31);
        }
    }
    /**
     * this method add chart template into document
     *
     * @param chartRelId relation id of chart in document relation file
     * @throws InvalidFormatException
     * @throws IOException
     * @since POI 4.0.0
     */
    @Internal
    public CTInline addChart(String chartRelId)
            throws InvalidFormatException, IOException {
        try {
            CTInline inline = run.addNewDrawing().addNewInline();

            //xml part of chart in document
            String xml =
                    "<a:graphic xmlns:a=\"" + CTGraphicalObject.type.getName().getNamespaceURI() + "\">" +
                            "<a:graphicData uri=\"" + CTChart.type.getName().getNamespaceURI() + "\">" +
                            "<c:chart xmlns:c=\"" + CTChart.type.getName().getNamespaceURI() + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" r:id=\"" + chartRelId + "\" />" +
                            "</a:graphicData>" +
                            "</a:graphic>";

            InputSource is = new InputSource(new StringReader(xml));

            org.w3c.dom.Document doc = DocumentHelper.readDocument(is);

            inline.set(XmlToken.Factory.parse(doc.getDocumentElement(), DEFAULT_XML_OPTIONS));

            // Setup the inline with 0 margin
            inline.setDistT(0);
            inline.setDistR(0);
            inline.setDistB(0);
            inline.setDistL(0);

            CTNonVisualDrawingProps docPr = inline.addNewDocPr();
            long id = getParent().getDocument().getDrawingIdManager().reserveNew();
            docPr.setId(id);
            //This name is not visible in Word anywhere.
            docPr.setName("chart " + id);

            return inline;
        } catch (XmlException | SAXException e) {
            throw new IllegalStateException(e);
        }
    }


    /**
     * Returns the embedded pictures of the run. These
     * are pictures which reference an external,
     * embedded picture image such as a .png or .jpg
     */
    public List<XWPFPicture> getEmbeddedPictures() {
        return pictures;
    }

    /**
     * Set the style ID for the run.
     *
     * @param styleId ID (not name) of the style to set for the run, e.g. "BoldItalic" (not "Bold Italic").
     */
    public void setStyle(String styleId) {
        CTRPr pr = getCTR().getRPr();
        if (null == pr) {
            pr = getCTR().addNewRPr();
        }
        CTString style = pr.getRStyle() != null ? pr.getRStyle() : pr.addNewRStyle();
        style.setVal(styleId);
    }



    /**
     * Returns the string version of the text and the phonetic string
     */
    @Override
    public String toString() {
        String phonetic = getPhonetic();
        if (phonetic.length() > 0) {
            return text() + " (" + phonetic + ")";
        } else {
            return text();
        }
    }

    /**
     * Returns the string version of the text, with tabs and
     * carriage returns in place of their xml equivalents.
     */
    @Override
    public String text() {
        StringBuilder text = new StringBuilder(64);

        // Grab the text and tabs of the text run
        // Do so in a way that preserves the ordering
        XmlCursor c = run.newCursor();
        c.selectPath("./*");
        while (c.toNextSelection()) {
            XmlObject o = c.getObject();
            if (o instanceof CTRuby) {
                handleRuby(o, text, false);
                continue;
            }
            _getText(o, text);
        }
        c.dispose();
        return text.toString();

    }

    /**
     * @return the phonetic (ruby) string associated with this run or an empty String if none exists
     */
    public String getPhonetic() {
        StringBuilder text = new StringBuilder(64);

        // Grab the text and tabs of the text run
        // Do so in a way that preserves the ordering
        XmlCursor c = run.newCursor();
        c.selectPath("./*");
        while (c.toNextSelection()) {
            XmlObject o = c.getObject();
            if (o instanceof CTRuby) {
                handleRuby(o, text, true);
            }
        }
        // Any picture text?
        if (pictureText != null && pictureText.length() > 0) {
            text.append("\n").append(pictureText).append("\n");
        }
        c.dispose();
        return text.toString();
    }

    /**
     * @param rubyObj         rubyobject
     * @param text            buffer to which to append the content
     * @param extractPhonetic extract the phonetic (rt) component or the base component
     */
    private void handleRuby(XmlObject rubyObj, StringBuilder text, boolean extractPhonetic) {
        XmlCursor c = rubyObj.newCursor();

        //according to the spec, a ruby object
        //has the phonetic (rt) first, then the actual text (base)
        //second.

        c.selectPath(".//*");
        boolean inRT = false;
        boolean inBase = false;
        while (c.toNextSelection()) {
            XmlObject o = c.getObject();
            if (o instanceof CTRubyContent) {
                String tagName = o.getDomNode().getNodeName();
                if ("w:rt".equals(tagName)) {
                    inRT = true;
                } else if ("w:rubyBase".equals(tagName)) {
                    inRT = false;
                    inBase = true;
                }
            } else {
                if (extractPhonetic && inRT) {
                    _getText(o, text);
                } else if (!extractPhonetic && inBase) {
                    _getText(o, text);
                }
            }
        }
        c.dispose();
    }

    private void _getText(XmlObject o, StringBuilder text) {

        if (o instanceof CTText) {
            String tagName = o.getDomNode().getNodeName();
            // Field Codes (w:instrText, defined in spec sec. 17.16.23)
            //  come up as instances of CTText, but we don't want them
            //  in the normal text output
            if (!"w:instrText".equals(tagName)) {
                text.append(((CTText) o).getStringValue());
            }
        }

        // Complex type evaluation (currently only for extraction of check boxes)
        if (o instanceof CTFldChar) {
            CTFldChar ctfldChar = ((CTFldChar) o);
            if (ctfldChar.getFldCharType() == STFldCharType.BEGIN) {
                if (ctfldChar.getFfData() != null) {
                    for (CTFFCheckBox checkBox : ctfldChar.getFfData().getCheckBoxList()) {
                        if (checkBox.getDefault() != null && checkBox.getDefault().getVal() == STOnOff.X_1) {
                            text.append("|X|");
                        } else {
                            text.append("|_|");
                        }
                    }
                }
            }
        }

        if (o instanceof CTPTab) {
            text.append('\t');
        }
        if (o instanceof CTBr) {
            text.append('\n');
        }
        if (o instanceof CTEmpty) {
            // Some inline text elements get returned not as
            //  themselves, but as CTEmpty, owing to some odd
            //  definitions around line 5642 of the XSDs
            // This bit works around it, and replicates the above
            //  rules for that case
            String tagName = o.getDomNode().getNodeName();
            if ("w:tab".equals(tagName) || "tab".equals(tagName)) {
                text.append('\t');
            }
            if ("w:br".equals(tagName) || "br".equals(tagName)) {
                text.append('\n');
            }
            if ("w:cr".equals(tagName) || "cr".equals(tagName)) {
                text.append('\n');
            }
        }
        if (o instanceof CTFtnEdnRef) {
            CTFtnEdnRef ftn = (CTFtnEdnRef) o;
            String footnoteRef = ftn.getDomNode().getLocalName().equals("footnoteReference") ?
                    "[footnoteRef:" + ftn.getId().intValue() + "]" : "[endnoteRef:" + ftn.getId().intValue() + "]";
            text.append(footnoteRef);
        }
    }

    /**
     * @see <a href="http://msdn.microsoft.com/en-us/library/ff533743(v=office.12).aspx">[MS-OI29500] Run Fonts</a>
     */
    public static enum FontCharRange {
        ascii /* char 0-127 */,
        cs /* complex symbol */,
        eastAsia /* east asia */,
        hAnsi /* high ansi */
    }

    /**
     * Set the text expand/collapse scale value.
     *
     * @param percentage The percentage to expand or compress the text
     * @since 4.0.0
     */
    public void setTextScale(int percentage) {
        CTRPr pr = getRunProperties(true);
        CTTextScale scale = pr.isSetW() ? pr.getW() : pr.addNewW();
        scale.setVal(percentage);
    }

    /**
     * Gets the current text scale value.
     *
     * @return Value is an integer percentage
     * @since 4.0.0
     */
    public int getTextScale() {
        CTRPr pr = getRunProperties(true);
        CTTextScale scale = pr.isSetW() ? pr.getW() : pr.addNewW();
        int value = scale.getVal();
        if (value == 0) {
            value = 100; // 100% scaling, that is, no change. See 17.3.2.43 w (Expanded/Compressed Text)
        }
        return value;
    }

    /**
     * Set the highlight color for the run. Silently does nothing of colorName is not a recognized value.
     *
     * @param colorName The name of the color as defined in the ST_HighlightColor simple type ({@link STHightlightColor})
     * @since 4.0.0
     */
    public void setTextHighlightColor(String colorName) {
        CTRPr pr = getRunProperties(true);
        CTHighlight highlight = pr.isSetHighlight() ? pr.getHighlight() : pr.addNewHighlight();
        STHighlightColor color = highlight.xgetVal();
        if (color == null) {
            color = STHighlightColor.Factory.newInstance();
        }
        STHighlightColor.Enum val = STHighlightColor.Enum.forString(colorName);
        if (val != null) {
            color.setStringValue(val.toString());
            highlight.xsetVal(color);
        }

    }

    /**
     * Gets the highlight color for the run
     *
     * @return {@link STHighlightColor} for the run.
     * @since 4.0.0
     */
    public STHighlightColor.Enum getTextHightlightColor() {
        CTRPr pr = getRunProperties(true);
        CTHighlight highlight = pr.isSetHighlight() ? pr.getHighlight() : pr.addNewHighlight();
        STHighlightColor color = highlight.xgetVal();
        if (color == null) {
            color = STHighlightColor.Factory.newInstance();
            color.set(STHighlightColor.NONE);
        }
        return (STHighlightColor.Enum)(color.enumValue());
    }

    /**
     * Get the vanish (hidden text) value
     *
     * @return True if the run is hidden text.
     * @since 4.0.0
     */
    public boolean isVanish() {
        CTRPr pr = getRunProperties(true);
        return pr != null && pr.isSetVanish() && isCTOnOff(pr.getVanish());
    }

    /**
     * The vanish (hidden text) property for the run.
     *
     * @param value Set to true to make the run hidden text.
     * @since 4.0.0
     */
    public void setVanish(boolean value) {
        CTRPr pr = getRunProperties(true);
        CTOnOff vanish = pr.isSetVanish() ? pr.getVanish() : pr.addNewVanish();
        vanish.setVal(value ? STOnOff.TRUE : STOnOff.FALSE);
    }

    /**
     * Get the vertical alignment value
     *
     * @return {@link STVerticalAlignRun.Enum} value (see 22.9.2.17 ST_VerticalAlignRun (Vertical Positioning Location))
     * @since 4.0.0
     */
    public STVerticalAlignRun.Enum getVerticalAlignment() {
        CTRPr pr = getRunProperties(true);
        CTVerticalAlignRun vertAlign = pr.isSetVertAlign() ? pr.getVertAlign() : pr.addNewVertAlign();
        STVerticalAlignRun.Enum val = vertAlign.getVal();
        if (val == null) {
            val = STVerticalAlignRun.BASELINE;
        }
        return val;
    }

    /**
     * Set the vertical alignment of the run.
     *
     * @param verticalAlignment Vertical alignment value, one of "baseline", "superscript", or "subscript".
     * @since 4.0.0
     */
    public void setVerticalAlignment(String verticalAlignment) {
        CTRPr pr = getRunProperties(true);
        CTVerticalAlignRun vertAlign = pr.isSetVertAlign() ? pr.getVertAlign() : pr.addNewVertAlign();
        STVerticalAlignRun align = vertAlign.xgetVal();
        if (align == null) {
            align = STVerticalAlignRun.Factory.newInstance();
        }
        STVerticalAlignRun.Enum val = STVerticalAlignRun.Enum.forString(verticalAlignment);
        if (val != null) {
            align.setStringValue(val.toString());
            vertAlign.xsetVal(align);
        }


    }

    /**
     * Get the emphasis mark value for the run.
     *
     * @return {@link STEm.Enum} emphasis mark type enumeration. See 17.18.24 ST_Em (Emphasis Mark Type).
     * @since 4.0.0
     */
    public STEm.Enum getEmphasisMark() {
        CTRPr pr = getRunProperties(true);
        CTEm emphasis = pr.isSetEm() ? pr.getEm() : pr.addNewEm();

        STEm.Enum val = emphasis.getVal();
        if (val == null) {
            val = STEm.NONE;
        }
        return val;
    }

    /**
     * Set the emphasis mark for the run. The emphasis mark goes above or below the run
     * text.
     *
     * @param markType Emphasis mark type name, e.g., "dot" or "none". See 17.18.24 ST_Em (Emphasis Mark Type)
     * @since 4.0.0
     */
    public void setEmphasisMark(String markType) {
        CTRPr pr = getRunProperties(true);
        CTEm emphasisMark = pr.isSetEm() ? pr.getEm() : pr.addNewEm();
        STEm mark = emphasisMark.xgetVal();
        if (mark == null) {
            mark = STEm.Factory.newInstance();
        }
        STEm.Enum val = STEm.Enum.forString(markType);
        if (val != null) {
            mark.setStringValue(val.toString());
            emphasisMark.xsetVal(mark);
        }


    }

    /**
     * Get the run properties for the run.
     *
     * @param create If true, create the properties, if false, do not.
     * @return The run properties or null if there are no properties and create is false.
     */
    protected CTRPr getRunProperties(boolean create) {
        CTRPr pr = run.isSetRPr() ? run.getRPr() : null;
        if (create && pr == null) {
            pr = run.addNewRPr();
        }
        return pr;
    }

}

一些问题

在实际开发中也遇到很多问题比如自定义的 不包含在一个 X W P F R u n 中,当然书签可以解决,但是为了展示字段直观,还是采用了字段替换,逐字段读取找到所有的 X W P F R u n 再替换,还有一个操作也能实现,但没必要,因为我提供的工具类完美解决了,感兴趣的可以按照如下操作试一下 1. 将 d o c x 后缀改成 z i p 2. 找到 w o r d / d o c u e n t . x m l 并打开 3. 修改或删除里面的 < w : t > {}不包含在一个XWPFRun中, 当然书签可以解决,但是为了展示字段直观,还是采用了字段替换, 逐字段读取找到所有的XWPFRun再替换,还有一个操作也能实现,但没必要,因为我提供的工具类完美解决了, 感兴趣的可以按照如下操作试一下 1.将docx后缀改成zip 2.找到 word/docuent.xml并打开 3.修改或删除里面的<w:t> 不包含在一个XWPFRun中,当然书签可以解决,但是为了展示字段直观,还是采用了字段替换,逐字段读取找到所有的XWPFRun再替换,还有一个操作也能实现,但没必要,因为我提供的工具类完美解决了,感兴趣的可以按照如下操作试一下1.docx后缀改成zip2.找到word/docuent.xml并打开3.修改或删除里面的<w:t>{name}</w:t>
docx文件的本质就是zip

补充内容

word转图片或pdf

点击此处有详细教程

Linux系统Word转换图片或PDF,字体出现乱码

点击此处有详细教程

表格嵌套问题

2023/12/11 21:19:00补充
表格中还存在表格的获取方式
通过XWPFTableCell中的getTables()方法获取表格
前文已经同步修改

总结

求个一键三连哦!!!!!!

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Java中,我们可以使用Apache POI库来操作Word文档。要将指定位置的字符替换为图片,可以按照以下步骤进行操作: 1. 首先,我们需要导入Apache POI的相关依赖包,例如poi、poi-ooxml等。 2. 创建一个新的Word文档对象,并加载要操作的Word文档。可以使用`XWPFDocument`类来实现。 ```java String filePath = "path/to/word/document.docx"; FileInputStream fileInputStream = new FileInputStream(filePath); XWPFDocument document = new XWPFDocument(fileInputStream); ``` 3. 使用`XWPFParagraph`类获取文档中的段落,并遍历每个段落,以便查找指定位置的字符。 ```java List<XWPFParagraph> paragraphs = document.getParagraphs(); for (XWPFParagraph paragraph : paragraphs) { String text = paragraph.getText(); if (text.contains("要替换的字符")) { // 在这里进行替换操作 } } ``` 4. 使用`getRuns()`方法获取每个段落中的文本运行对象,并遍历每个运行对象。 ```java List<XWPFRun> runs = paragraph.getRuns(); for (XWPFRun run : runs) { String runText = run.getText(0); if (runText.contains("要替换的字符")) { // 在这里进行替换操作 } } ``` 5. 在要替换的位置创建新的`XWPFRun`对象,并使用`createPicture()`方法将图片插入到文本中。 ```java byte[] pictureBytes = getPictureBytes(); // 获取图片的字节数组 int pictureType = Document.PICTURE_TYPE_PNG; // 图片类型,例如PNG、JPEG等 int width = Units.toEMU(100); // 图片宽度,单位是EMU int height = Units.toEMU(100); // 图片高度,单位是EMU int pictureIndex = document.getNextPicNameNumber(pictureType); // 获取下一个图片索引 run.setText(runText.replace("要替换的字符", "")); // 删除原始文本 run.addPicture(new ByteArrayInputStream(pictureBytes), pictureType, "image.png", width, height); // 插入图片 ``` 6. 最后,将修改后的文档保存为新的Word文档。 ```java String outputPath = "path/to/output/document.docx"; FileOutputStream fileOutputStream = new FileOutputStream(outputPath); document.write(fileOutputStream); fileOutputStream.close(); document.close(); ``` 通过以上步骤,我们可以在指定位置将字符替换为图片,并将修改后的文档保存到新的Word文档中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值