package com.czmiracle.word.test.wordtest;
import org.apache.commons.lang3.StringUtils;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.finders.ClassFinder;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.*;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.math.BigInteger;
import java.util.Objects;
import java.util.UUID;
/**
* @author hongrongjin
* @create 2020-09-14 10:56 上午
**/
public class ResizeTableFontService {
/**
* 字号与字宽的比例
*/
private static BigInteger FONT_TIMES = BigInteger.valueOf(10L);
/**
* 单元格内的字数超过数量后开始计算是否缩放
*/
private static int RESIZE_LIMIT_TEXT_AMOUNT = 30;
/**
* 测试方法,如果项目中,只要调用analysisPackage方法
*/
public static void resizeTableFont() {
//读取文件
WordprocessingMLPackage wordprocessingMLPackage = null;
File file = new File("/Users/hongrongjin/Desktop/a.docx");
try {
wordprocessingMLPackage = Docx4J.load(file);
} catch (Docx4JException e) {
e.printStackTrace();
return;
}
//开始分析
analysisPackage(wordprocessingMLPackage);
//重新存入文件
String uuid = UUID.randomUUID().toString();
File outFile = new File("/Users/hongrongjin/Desktop/a-" + uuid + ".docx");
try {
Docx4J.save(wordprocessingMLPackage, outFile);
} catch (Docx4JException e) {
e.printStackTrace();
}
}
/**
* 开始解析word
*
* @param wordprocessingMLPackage
*/
public static void analysisPackage(WordprocessingMLPackage wordprocessingMLPackage) {
ClassFinder tblFinder = new ClassFinder(Tbl.class);
new TraversalUtil(wordprocessingMLPackage.getMainDocumentPart(), tblFinder);
//找到所有的table
tblFinder.results.stream().forEach(tblItem -> {
Tbl tbl = (Tbl) tblItem;
//开始分析table
analysisTbl(tbl);
});
}
/**
* 循环解析tbl
*
* @param tbl
*/
public static void analysisTbl(Tbl tbl) {
ClassFinder trFinder = new ClassFinder(Tr.class);
new TraversalUtil(tbl, trFinder);
//找到所有该table中的的tr
trFinder.results.stream().forEach(trItem -> {
Tr tr = (Tr) trItem;
//开始分析tr
analysisTr(tbl, tr);
});
}
/**
* 循环解析tr
*
* @param tbl
* @param tr
*/
public static void analysisTr(Tbl tbl, Tr tr) {
BigInteger trHeight = getTrHeight(tr);
ClassFinder tcFinder = new ClassFinder(Tc.class);
new TraversalUtil(tr, tcFinder);
//找到所有该tr中的单元格
tcFinder.results.stream()
.filter(tcItem -> {
//筛选掉字符串长度小于30的的内容,不去分析
Tc tc = (Tc) tcItem;
String content = StringUtils.join(tc.getContent());
content = content.trim();
return StringUtils.length(content) >= RESIZE_LIMIT_TEXT_AMOUNT;
})
.forEach(tcItem -> {
Tc tc = (Tc) tcItem;
//开始分析单元格
FontResult fontResult = analysisTc(tbl, tc, trHeight, null);
if (fontResult.needUpdate) {
//如果需要更新,那就开始跟新里面的字体
updateTc(tc, fontResult.fontSize);
}
});
}
/**
* 循环解析tc
*
* @param tbl
* @param tc
* @param trHeight 单元格高度
* @param inFontSize (通过递归传进来的fontSize)
*/
public static FontResult analysisTc(Tbl tbl, Tc tc, BigInteger trHeight, BigInteger inFontSize) {
FontResult fontResult = new FontResult();
//第一次inFontSize肯定是null
//如果有传进来inFontSize,说明肯定是第二次进了,第二次肯定是开始缩小字体了,一定要更新了;
//如果没有传进来,说明肯定是第一次进来;需不需要更新还要看后续校验;
boolean hasFontSize = !(inFontSize == null);
fontResult.needUpdate = hasFontSize;
//单元格宽度
BigInteger cellWidth = tc.getTcPr().getTcW().getW();
//左边距
BigInteger left = getLeft(tbl, tc);
//右边距
BigInteger right = getRight(tbl, tc);
//上边距
BigInteger top = getTop(tbl, tc);
//下边距
BigInteger bottom = getBottom(tbl, tc);
//开始找P段落
ClassFinder pFinder = new ClassFinder(P.class);
new TraversalUtil(tc, pFinder);
//如果里面没有段落就说明不需要做什么,直接跳过
if (!CollectionUtils.isEmpty(pFinder.results)) {
//开始计算总高度
BigInteger allHeight = pFinder.results.stream().map(pItem -> {
P p = (P) pItem;
//当前字号数
BigInteger currentFontSize = p.getPPr().getRPr().getSz().getVal();
//如果带进来的字号,那就用带进来的字号计算
fontResult.fontSize = hasFontSize ? inFontSize : currentFontSize;
//字宽度,字号乘以倍数
BigInteger fontWidth = hasFontSize ? inFontSize.multiply(FONT_TIMES) : currentFontSize.multiply(FONT_TIMES);
//左边距、边距
BigInteger leftInd = BigInteger.ZERO, rightInd = BigInteger.ZERO;
PPrBase.Ind ind = p.getPPr().getInd();
if (ind != null) {
if (ind.getLeftChars() != null) {
leftInd = ind.getLeftChars().divide(BigInteger.valueOf(100L));
}
if (ind.getRightChars() != null) {
rightInd = ind.getRightChars().divide(BigInteger.valueOf(100L));
}
}
//行间距=字宽(word中设置段落行间距-固定值)
BigInteger lineHeight = fontWidth;
//一行的文字数
BigInteger singleLineWords = (cellWidth.subtract(left).subtract(right)).divide(fontWidth).subtract(leftInd).subtract(rightInd);
//获取内容
String content = getText(p);
//计算行数,一定要到小数点。
BigInteger[] lineArray = BigInteger.valueOf(content.length()).divideAndRemainder(singleLineWords);
//是否有余数,有余数则为要加1
BigInteger lines = lineArray[1].compareTo(BigInteger.ZERO) == 1 ? lineArray[0].add(BigInteger.ONE) : lineArray[0];
//高度
BigInteger height = lineHeight.multiply(lines);
return height;
}).reduce(BigInteger.ZERO, BigInteger::add);
//开始比较
if (allHeight.compareTo(trHeight) == 1) {
//高度大于单元格高度,减少字号;并进入递归,这时候把小一号的字体传入,让其计算。这时候就可以看出,肯定是要去更新这个单元格的字体了
return analysisTc(tbl, tc, trHeight, fontResult.fontSize.subtract(BigInteger.ONE));
} else {
//如果小于。那是否更新就是看有没有传入infontsize
return fontResult;
}
}
//如果什么都没做,肯定不要更新
fontResult.needUpdate = false;
return fontResult;
}
/**
* 更新字体
* @param tc
* @param fontSize
*/
public static void updateTc(Tc tc, BigInteger fontSize) {
ClassFinder rFinder = new ClassFinder(R.class);
new TraversalUtil(tc, rFinder);
rFinder.results.stream().forEach(rItem -> {
R r = (R) rItem;
r.getRPr().getSz().setVal(fontSize);
});
}
/**
* 获取tr的高度
*
* @param tr
* @return
*/
public static BigInteger getTrHeight(Tr tr) {
BigInteger trHeight =
tr.getTrPr().getCnfStyleOrDivIdOrGridBefore().stream().map(jaxbElement -> {
CTHeight ctHeight = null;
if (jaxbElement.getValue() instanceof CTHeight) {
ctHeight = (CTHeight) jaxbElement.getValue();
}
BigInteger result;
if (ctHeight == null) {
result = BigInteger.valueOf(0L);
} else {
result = ctHeight.getVal();
}
return result;
}).reduce(BigInteger.ZERO, BigInteger::add);
return trHeight;
}
/**
* 从tc上找,如果找不到从tbl上找,getLeft、Right、Bottom同理
*
* @param tbl
* @param tc
* @return
*/
public static BigInteger getTop(Tbl tbl, Tc tc) {
if (tc.getTcPr() == null || tc.getTcPr().getTcMar() == null) {
return tbl.getTblPr().getTblCellMar().getTop().getW();
} else {
return tc.getTcPr().getTcMar().getTop().getW();
}
}
public static BigInteger getBottom(Tbl tbl, Tc tc) {
if (tc.getTcPr() == null || tc.getTcPr().getTcMar() == null) {
return tbl.getTblPr().getTblCellMar().getBottom().getW();
} else {
return tc.getTcPr().getTcMar().getBottom().getW();
}
}
public static BigInteger getRight(Tbl tbl, Tc tc) {
if (tc.getTcPr() == null || tc.getTcPr().getTcMar() == null) {
return tbl.getTblPr().getTblCellMar().getRight().getW();
} else {
return tc.getTcPr().getTcMar().getRight().getW();
}
}
public static BigInteger getLeft(Tbl tbl, Tc tc) {
if (tc.getTcPr() == null || tc.getTcPr().getTcMar() == null) {
return tbl.getTblPr().getTblCellMar().getLeft().getW();
} else {
return tc.getTcPr().getTcMar().getLeft().getW();
}
}
/**
* 获取段落的所有文字
* @param p
* @return
*/
public static String getText(P p) {
ClassFinder textFinder = new ClassFinder(Text.class);
new TraversalUtil(p, textFinder);
return textFinder.results
.stream()
.map(item -> {
if (item instanceof Text) {
return (Text) item;
}
return null;
})
.filter(t -> Objects.nonNull(t.getValue()))
.map(Text::getValue)
.reduce("", String::concat);
}
/**
* result的一个类,其实用map也行。
*/
private static class FontResult {
//是否需要更新
public boolean needUpdate;
//更新的fontSize
public BigInteger fontSize;
}
}