背景
之前有的教輔標註需求,在匯出題庫的時候希望順便匯出可以檢視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