使用spire.doc导出支持编辑Latex公式的标准格式word

背景

之前有的教辅标注需求,在导出题库的时候希望顺便导出可以查看word,方便线下预览成品效果,因为只是用来预览并且为了沿用前端的样式,当时方案就是直接生成html,写个word的文件头,这样就可以用word打开查看了,文件头如下:

<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40">
</html>

直接在html里面填充内容即可,将后缀一改,word(伪)就生成了,但是这样的word有个致命的缺陷(客户机器必须联网,否则word中图片无法加载),并且随着业务的发展,导出的word客户想再次导入系统,这种html格式无法正常识别。需要导出标准格式的word并且顺便提了个需求,希望标注中latex公式在word中可以编辑

常见的word导出方案

  • Apache POI
  • FreeMark模板引擎生成xml格式文档
  • Aspose word(商业付费)
  • Spire.Doc(有商业版也有免费版)

其中AsposePDF for JAVA是从V19.4版本开始支持Latex公式的;使用Apache POI基本上是把Latex转成mathMl然后再写入到word中去,需要用到fmath两个jar包,在网上找了一些,没有找到正规渠道,先pass;FreeMark写入其实也用到了fmath,需要将latex转成mathml格式,再写入到word的xml模板中,不过二次加工调用的引擎不支持xml编码的格式的word,所以此方案也pass

使用Spire.Doc导出支持编辑latex公式的word

依赖

<dependency>
    <groupId>e-iceblue</groupId>
    <artifactId>spire.doc.free</artifactId>
    <version>3.9.0</version>
</dependency>

创建Document对象和Section

Document document = new Document();
Section section = document.addSection();

创建段落,设置前后边距

Paragraph paragraph = section.addParagraph();
paragraph.getFormat().setLineSpacing(15);
paragraph.getFormat().setBeforeSpacing(20);

写入文字及设置中英文字体

TextRange textRange = paragraph.appendText(text);
textRange.getCharacterFormat().setFontNameFarEast("宋体");
textRange.getCharacterFormat().setFontNameNonFarEast("Times New Roman");

写入latex公式

OfficeMath math = new OfficeMath(paragraph.getDocument());
paragraph.getItems().add(math);
math.fromLatexMathCode(latexFormat(innerPojo.latex));

/**
 * 这里spire.doc有一些缺陷,对于一些符号支持的不是很好,大于等于小于等于,这里做一下替换,连续中文也做了\mbox{}包裹,这个是基于latex的经验,但是实际并没有什么用,spire.doc不支持带有中文的latex公式渲染,可能版本太低了,所以不能正常渲染就直接显示图片
 */
private String latexFormat(String latex) {
    if (latex.contains("leqslant")) {
        latex = latex.replace("leqslant", "leq");
    }
    if (latex.contains("geqslant")) {
        latex = latex.replace("geqslant", "geq");
    }
    StringBuilder latexBuilder = new StringBuilder();
    boolean isChinese = false;
    String regexStr = "[\u4E00-\u9FA5]";
    for (Character c : latex.toCharArray()) {
        Matcher chineseMatch = Pattern.compile(regexStr).matcher(c.toString());
        if (chineseMatch.find()) {
            if (isChinese) {
                latexBuilder.append(c);
            } else {
                latexBuilder.append("\\mbox{").append(c);
                isChinese = true;
            }
            continue;
        } else {
            if (isChinese) {
                isChinese = false;
                latexBuilder.append("}");
            }
            latexBuilder.append(c);
        }
    }
    return latexBuilder.toString();
}

绘制表格
spire.doc中api中无法直接再段落中直接添加表格,需要先添加一个文本框,然后在文本框的内部再添加表格,这里事先把表格的二维数组绘制好

TextBox textBox = paragraph.appendTextBox(500, 20 * innerPojo.rows);
textBox.getFormat().setHorizontalAlignment(ShapeHorizontalAlignment.Inside);
textBox.getFormat().setNoLine(true);
Table table = textBox.getBody().addTable(true);
table.resetCells(innerPojo.rows, innerPojo.lines);
for (int i = 0; i < innerPojo.rowLines.size(); i++) {
    List<String> rowLine = innerPojo.rowLines.get(i);
    for (int j = 0; j < rowLine.size(); j++) {
        appendWithFont(rowLine.get(j), table.get(i, j).addParagraph());
    }
}
// 设置文本框样式嵌入型
textBox.setTextWrappingStyle(TextWrappingStyle.Inline);

这个版本的api找了一下,没有发现可以让文本框的高度自适应的方法,或许收费版本会好很多

插入图片
控制一下图片宽度不要超过500,高度不要超过300

DocPicture picture = paragraph.appendPicture(innerPojo.getImage());
log.info("pictureSize,Width:{},Height:{}", picture.getWidth(), picture.getHeight());
if (picture.getWidth() > 500) {
    BigDecimal rate = BigDecimal.valueOf(500).divide(BigDecimal.valueOf(picture.getWidth()), 8, BigDecimal.ROUND_DOWN);
    picture.setHeight(picture.getHeight() * rate.floatValue());
    picture.setWidth(500);
} else if (picture.getHeight() > 300) {
    BigDecimal rate = BigDecimal.valueOf(300).divide(BigDecimal.valueOf(picture.getHeight()), 8, BigDecimal.ROUND_DOWN);
    picture.setWidth(picture.getWidth() * rate.floatValue());
    picture.setHeight(300);
}

导出效果如下图:

后续有时间再实验一下poi方式的公式导出

参考连接

https://www.e-iceblue.cn/spiredocforjavatext/set-character-format-in-word-in-java.html

posted @ 2021-10-30 22:39  蒲公英的狂想  阅读(1000)  评论(4编辑  收藏  举报