使用spire.doc匯出支援編輯Latex公式的標準格式word

蒲公英的狂想發表於2021-10-30

背景

之前有的教輔標註需求,在匯出題庫的時候希望順便匯出可以檢視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(有商業版也有免費版)

其中Aspose word是不支援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

相關文章