Docx4j缩放字体

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;
    }

}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值