基于 Apache POI 实现的 Word 操作工具类

发布于:2025-06-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

基于 Apache POI 实现的 Word 操作工具类

这个工具类是让 AI 写的,已覆盖常用功能。
如不满足场景的可以让 AI 继续加功能。

效果图

已包含的功能:

  • 文本相关: 添加文本、 设置字体颜色、 设置字体大小、 设置对齐方式、 设置字符间距、 设置字体加粗、 设置字符缩进、 设置段落行高、 两段文本两端对齐、 添加水平线、同一段文本中,关键字样式特殊定义
  • 表格相关: 添加表格、设置表头样式、设置内容单元格样式、单元格跨行跨列
  • 其他:替换数字字母的字体为 times new roman、设置页眉页脚

POI 的版本是 4.1.1

import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.Map;

/**
 * WordGenerator 是一个用于生成Word文档的工具类,支持链式编程。
 * create by cursor: claude-4-sonnet
 */
public class WordGenerator {

    private XWPFDocument document;
    private XWPFParagraph currentParagraph;
    private List<XWPFRun> currentRuns;
    private long pageWidthTwips;
    private long leftMarginTwips;
    private long rightMarginTwips;
    private long topMarginTwips;
    private long bottomMarginTwips;

    public WordGenerator() {
        this.document = new XWPFDocument();
        this.currentParagraph = document.createParagraph();
        this.currentRuns = new ArrayList<>();
        this.currentRuns.add(currentParagraph.createRun());
    }

    /**
     * 初始化文档设置,包括纸张大小、方向和边距。
     * @param size 纸张大小
     * @param orientation 页面方向
     * @param topMargin 顶部边距 (厘米)
     * @param rightMargin 右侧边距 (厘米)
     * @param bottomMargin 底部边距 (厘米)
     * @param leftMargin 左侧边距 (厘米)
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator initDocument(PaperSize size, PageOrientation orientation, double topMargin, double rightMargin, double bottomMargin, double leftMargin) {
        CTSectPr sectPr = document.getDocument().getBody().addNewSectPr();
        // 设置纸张大小
        CTPageSz pageSz = sectPr.addNewPgSz();
        switch (size) {
            case A4:
                pageSz.setW(BigInteger.valueOf(11906)); // A4 width in twips
                pageSz.setH(BigInteger.valueOf(16838)); // A4 height in twips
                this.pageWidthTwips = 11906;
                break;
            case A3:
                pageSz.setW(BigInteger.valueOf(16838)); // A3 width in twips
                pageSz.setH(BigInteger.valueOf(23811)); // A3 height in twips
                this.pageWidthTwips = 16838;
                break;
            default:
                // 默认A4
                pageSz.setW(BigInteger.valueOf(11906));
                pageSz.setH(BigInteger.valueOf(16838));
                this.pageWidthTwips = 11906;
                break;
        }

        // 设置页面方向
        if (orientation == PageOrientation.LANDSCAPE) {
            pageSz.setOrient(STPageOrientation.LANDSCAPE);
            BigInteger width = pageSz.getW();
            pageSz.setW(pageSz.getH());
            pageSz.setH(width);
            this.pageWidthTwips = pageSz.getW().longValue(); // 更新横向时的宽度
        } else {
            pageSz.setOrient(STPageOrientation.PORTRAIT);
        }

        // 设置页面边距 (厘米转换为twips)
        CTPageMar pageMar = sectPr.addNewPgMar();
        this.topMarginTwips = Math.round(topMargin * 567);
        this.rightMarginTwips = Math.round(rightMargin * 567);
        this.bottomMarginTwips = Math.round(bottomMargin * 567);
        this.leftMarginTwips = Math.round(leftMargin * 567);

        pageMar.setTop(BigInteger.valueOf(this.topMarginTwips));
        pageMar.setRight(BigInteger.valueOf(this.rightMarginTwips));
        pageMar.setBottom(BigInteger.valueOf(this.bottomMarginTwips));
        pageMar.setLeft(BigInteger.valueOf(this.leftMarginTwips));
        return this;
    }

    /**
     * 添加文本到当前段落。
     * @param text 要添加的文本
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator addText(String text) {
        this.currentParagraph = document.createParagraph();
        XWPFRun run = currentParagraph.createRun();
        if (text == null || text.isEmpty()) {
            run.setText("\u00A0"); // 使用非中断空格
        } else {
            run.setText(text);
        }
        this.currentRuns = Collections.singletonList(run);
        return this;
    }

    /**
     * 支持关键字高亮样式的文本添加。
     * @param text 原始文本
     * @param keywordStyles 关键字及其样式(区分大小写)
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator addText(String text, Map<String, TextStyle> keywordStyles) {
        this.currentParagraph = document.createParagraph();
        this.currentRuns = new ArrayList<>();
        if (text == null || text.isEmpty() || keywordStyles == null || keywordStyles.isEmpty()) {
            XWPFRun run = currentParagraph.createRun();
            run.setText(text == null ? "\u00A0" : text);
            this.currentRuns.add(run);
            return this;
        }
        int idx = 0;
        while (idx < text.length()) {
            int matchStart = -1, matchEnd = -1;
            String matchedKey = null;
            // 优先匹配最长关键字
            for (String key : keywordStyles.keySet()) {
                if (key.isEmpty()) continue;
                if (text.startsWith(key, idx)) {
                    if (matchedKey == null || key.length() > matchedKey.length()) {
                        matchedKey = key;
                        matchStart = idx;
                        matchEnd = idx + key.length();
                    }
                }
            }
            if (matchedKey != null) {
                // 关键字片段
                XWPFRun run = currentParagraph.createRun();
                run.setText(matchedKey);
                keywordStyles.get(matchedKey).apply(run);
                this.currentRuns.add(run);
                idx = matchEnd;
            } else {
                // 普通片段
                int nextKeyIdx = text.length();
                for (String key : keywordStyles.keySet()) {
                    int pos = text.indexOf(key, idx);
                    if (pos != -1 && pos < nextKeyIdx) {
                        nextKeyIdx = pos;
                    }
                }
                String normal = text.substring(idx, nextKeyIdx);
                XWPFRun run = currentParagraph.createRun();
                run.setText(normal);
                this.currentRuns.add(run);
                idx = nextKeyIdx;
            }
        }
        return this;
    }

    /**
     * 文本样式封装类。
     */
    public static class TextStyle {
        private String fontFamily;
        private Integer fontSize;
        private String color;
        private Boolean bold;
        // 可扩展更多样式

        public TextStyle() {}
        public TextStyle setFontFamily(String fontFamily) { this.fontFamily = fontFamily; return this; }
        public TextStyle setFontSize(Integer fontSize) { this.fontSize = fontSize; return this; }
        public TextStyle setColor(String color) { this.color = color; return this; }
        public TextStyle setBold(Boolean bold) { this.bold = bold; return this; }

        public void apply(XWPFRun run) {
            if (fontFamily != null) run.setFontFamily(fontFamily);
            if (fontSize != null) run.setFontSize(fontSize);
            if (color != null) run.setColor(color);
            if (bold != null) run.setBold(bold);
        }
    }

    /**
     * 设置当前文本的字体族。
     * @param fontFamily 字体族名称 (例如 "仿宋", "宋体", "Arial")
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setFontFamily(String fontFamily) {
        if (currentRuns != null) {
            for (XWPFRun run : currentRuns) {
                run.setFontFamily(fontFamily);
            }
        }
        return this;
    }

    /**
     * 设置当前文本的颜色。
     * @param hexColor 颜色值的十六进制字符串 (例如 "FF0000" 为红色)
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setColor(String hexColor) {
        if (currentRuns != null) {
            for (XWPFRun run : currentRuns) {
                run.setColor(hexColor);
            }
        }
        return this;
    }

    /**
     * 设置当前文本的字号。
     * @param sizeInPoints 字号大小 (磅)
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setFontSize(int sizeInPoints) {
        if (currentRuns != null) {
            for (XWPFRun run : currentRuns) {
                run.setFontSize(sizeInPoints);
            }
        }
        return this;
    }

    /**
     * 设置当前文本是否加粗。
     * @param bold true 为加粗,false 为不加粗
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setBold(boolean bold) {
        if (currentRuns != null) {
            for (XWPFRun run : currentRuns) {
                run.setBold(bold);
            }
        }
        return this;
    }

    /**
     * 设置当前段落的对齐方式。
     * @param alignment 对齐方式
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setAlignment(ParagraphAlignment alignment) {
        if (currentParagraph != null) {
            currentParagraph.setAlignment(alignment);
        }
        return this;
    }

    /**
     * 设置当前段落的首行缩进或左侧缩进。
     * @param value 缩进值
     * @param unit 缩进单位
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setIndentation(int value, IndentUnit unit) {
        if (currentParagraph != null) {
            long twips = 0;
            switch (unit) {
                case CM:
                    twips = Math.round(value * 567); // 1cm = 567 twips
                    break;
                case POINTS:
                    twips = value * 20; // 1pt = 20 twips
                    break;
                case TWIPS:
                    twips = value;
                    break;
            }
            currentParagraph.setIndentationFirstLine((int) twips);
        }
        return this;
    }

    /**
     * 添加标题。
     * @param title 标题文本
     * @param level 标题级别
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator addTitle(String title, TitleLevel level) {
        XWPFParagraph titleParagraph = document.createParagraph();
        titleParagraph.setAlignment(ParagraphAlignment.CENTER); // 默认居中

        // 只有当标题不为空时才设置样式
        if (title != null && !title.isEmpty()) {
            switch (level) {
                case MAIN_TITLE:
                    titleParagraph.setStyle("Title");
                    break;
                case SUB_TITLE_1:
                    titleParagraph.setStyle("Heading1");
                    titleParagraph.setAlignment(ParagraphAlignment.LEFT);
                    break;
                case SUB_TITLE_2:
                    titleParagraph.setStyle("Heading2");
                    titleParagraph.setAlignment(ParagraphAlignment.LEFT);
                    break;
                default:
                    titleParagraph.setStyle("Normal");
                    break;
            }
        }

        XWPFRun titleRun = titleParagraph.createRun();
        if (title == null || title.isEmpty()) {
            titleRun.setText("\u00A0"); // 使用非中断空格
        } else {
            titleRun.setText(title);
        }

        // 保存当前的段落和运行对象
        this.currentParagraph = titleParagraph;
        this.currentRuns = Collections.singletonList(titleRun);

        return this;
    }

    /**
     * 新增:原有不带列样式参数的 addTable 方法
     * @param data 表格数据 (List<List<String>>)
     * @param headerStartRowIndex 表头起始行索引 (从0开始)
     * @param headerEndRowIndex 表头结束行索引 (从0开始,包含此行)
     * @param headerStyle 表头文本样式
     * @param cellStyle 单元格文本样式
     * @param mergeRegions 合并区域列表,每个元素为 [rowIndex, colIndex, rowSpan, colSpan]
     * @param borderType 表格边框类型
     * @param borderWidth 表格边框粗细 (磅)
     * @param borderColor 表格边框颜色 (十六进制字符串)
     * @param defaultRowHeight 默认行高 (twips)
     * @param customRowHeights 自定义行高 Map<行索引, 高度>
     * @param customColumnWidths 自定义列宽 Map<列索引, 宽度>
     * @param defaultColumnWidth 默认列宽 (twips),如果为 null 则自动计算
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator addTable(
            List<List<String>> data,
            int headerStartRowIndex,
            int headerEndRowIndex,
            TableTextStyle headerStyle,
            TableTextStyle cellStyle,
            List<int[]> mergeRegions,
            XWPFTable.XWPFBorderType borderType,
            int borderWidth,
            String borderColor,
            int defaultRowHeight,
            Map<Integer, Integer> customRowHeights,
            Map<Integer, Integer> customColumnWidths,
            Integer defaultColumnWidth) {
        return addTableInternal(data, headerStartRowIndex, headerEndRowIndex, headerStyle, cellStyle, mergeRegions, borderType, borderWidth, borderColor, defaultRowHeight, customRowHeights, customColumnWidths, defaultColumnWidth);
    }

    /**
     * 合并单元格 (水平方向)。
     * @param table 表格对象
     * @param row 合并的行索引
     * @param fromCol 起始列索引
     * @param toCol 结束列索引
     */
    public void mergeCellsHorizontally(XWPFTable table, int row, int fromCol, int toCol) {
        XWPFTableCell cell = table.getRow(row).getCell(fromCol);
        // Sets the first merged cell to restart merge
        CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();
        if (tcPr.isSetHMerge()) {
            tcPr.unsetHMerge();
        }
        tcPr.addNewHMerge().setVal(STMerge.RESTART);
        // Clear content of merged cells
        for (int colIndex = fromCol + 1; colIndex <= toCol; colIndex++) {
            XWPFTableCell nextCell = table.getRow(row).getCell(colIndex);
            if (nextCell == null) {
                nextCell = table.getRow(row).addNewTableCell(); // Create new cell if null
            }
            clearCell(nextCell);
            tcPr = nextCell.getCTTc().isSetTcPr() ? nextCell.getCTTc().getTcPr() : nextCell.getCTTc().addNewTcPr();
            if (tcPr.isSetHMerge()) {
                tcPr.unsetHMerge();
            }
            tcPr.addNewHMerge().setVal(STMerge.CONTINUE);
        }
    }

    /**
     * 合并单元格 (垂直方向)。
     * @param table 表格对象
     * @param col 合并的列索引
     * @param fromRow 起始行索引
     * @param toRow 结束行索引
     */
    public void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
        // Start merging from the first row
        XWPFTableCell cell = table.getRow(fromRow).getCell(col);
        // Sets the first merged cell to restart merge
        CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();
        if (tcPr.isSetVMerge()) {
            tcPr.unsetVMerge();
        }
        tcPr.addNewVMerge().setVal(STMerge.RESTART);

        // Merge remaining cells
        for (int i = fromRow + 1; i <= toRow; i++) {
            XWPFTableCell cellToMerge = table.getRow(i).getCell(col);
            if (cellToMerge == null) {
                cellToMerge = table.getRow(i).addNewTableCell(); // Create new cell if null
            }
            clearCell(cellToMerge);
            tcPr = cellToMerge.getCTTc().isSetTcPr() ? cellToMerge.getCTTc().getTcPr() : cellToMerge.getCTTc().addNewTcPr();
            if (tcPr.isSetVMerge()) {
                tcPr.unsetVMerge();
            }
            tcPr.addNewVMerge().setVal(STMerge.CONTINUE);
        }
    }

    private void clearCell(XWPFTableCell cell) {
        for (int i = cell.getParagraphs().size(); i > 0; i--) {
            cell.removeParagraph(0);
        }
        cell.addParagraph();
    }

    /**
     * 保存Word文档。
     * @param filePath 保存路径
     * @throws IOException 如果保存失败
     */
    public void save(String filePath) throws IOException {
        try (FileOutputStream out = new FileOutputStream(filePath)) {
            document.write(out);
        }
    }

    // --- 枚举定义 ---

    public enum PaperSize {
        A4, A3
    }

    public enum PageOrientation {
        PORTRAIT, LANDSCAPE
    }

    public enum IndentUnit {
        CM, POINTS, TWIPS
    }

    public enum TitleLevel {
        MAIN_TITLE, SUB_TITLE_1, SUB_TITLE_2
    }

    public enum ChineseFontSize {
        // 中文字号到磅值的映射
        CHU("初号", 42),
        XIAOCHU("小初", 36),
        YI("一号", 26),
        XIAOYI("小一", 24),
        ER("二号", 22),
        XIAOER("小二", 18),
        SAN("三号", 16),
        XIAOSAN("小三", 15),
        SI("四号", 14),
        XIAOSI("小四", 12),
        WU("五号", 10.5),
        XIAOWU("小五", 9),
        LIU("六号", 7.5),
        XIAOLIU("小六", 6.5),
        QI("七号", 5.5),
        BA("八号", 5);

        private final String name;
        private final double points;

        ChineseFontSize(String name, double points) {
            this.name = name;
            this.points = points;
        }

        public int getPoints() {
            return (int) points;
        }

        public static int getPoints(String chineseSize) {
            for (ChineseFontSize size : values()) {
                if (size.name.equals(chineseSize)) {
                    return (int) size.points;
                }
            }
            return 12; // 默认返回小四号
        }
    }

    /**
     * 设置当前文本的字号(支持中文字号)。
     * @param chineseSize 中文字号(如"三号"、"四号"等)
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setFontSize(String chineseSize) {
        if (currentRuns != null) {
            for (XWPFRun run : currentRuns) {
                run.setFontSize(ChineseFontSize.getPoints(chineseSize));
            }
        }
        return this;
    }

    /**
     * 设置当前文本的字号(支持中文字号枚举)。
     * @param chineseSize 中文字号枚举
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setFontSize(ChineseFontSize chineseSize) {
        if (currentRuns != null) {
            for (XWPFRun run : currentRuns) {
                run.setFontSize(chineseSize.getPoints());
            }
        }
        return this;
    }

    /**
     * 设置当前文本的字符间距。
     * @param value 间距值
     * @param unit 间距单位
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setCharacterSpacing(double value, IndentUnit unit) {
        if (currentRuns != null) {
            long twips = 0;
            switch (unit) {
                case CM:
                    twips = Math.round(value * 567); // 1cm = 567 twips
                    break;
                case POINTS:
                    twips = Math.round(value * 20); // 1pt = 20 twips
                    break;
                case TWIPS:
                    twips = Math.round(value); // 直接使用twips值
                    break;
            }
            for (XWPFRun run : currentRuns) {
                run.setCharacterSpacing((int) twips);
            }
        }
        return this;
    }

    /**
     * 应用公文模式字体样式:将数字、字母的字体改为 Times New Roman,不影响中文字体。
     * 此方法会遍历整个文档的每个段落和每个文本运行。
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator applyOfficialDocumentFontStyling() {
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            for (XWPFRun run : paragraph.getRuns()) {
                CTRPr rpr = run.getCTR().isSetRPr() ? run.getCTR().getRPr() : run.getCTR().addNewRPr();
                CTFonts fonts = rpr.isSetRFonts() ? rpr.getRFonts() : rpr.addNewRFonts();

                // 设置 ASCII 和 High ANSI 字符的字体为 Times New Roman
                fonts.setAscii("Times New Roman");
                fonts.setHAnsi("Times New Roman");
                // 不设置 East Asia (中文字符) 的字体,使其保持默认或由Word自动处理
            }
        }
        return this;
    }

    /**
     * 添加两端对齐的文本(同一行内,一个文本靠左,一个文本靠右)。
     * @param leftText 左侧文本
     * @param rightText 右侧文本
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator addTwoEndsText(String leftText, String rightText) {
        XWPFParagraph paragraph = document.createParagraph();
        paragraph.setAlignment(ParagraphAlignment.BOTH); // 两端对齐

        // 创建制表符
        CTP ctp = paragraph.getCTP();
        CTPPr ctppr = ctp.addNewPPr();
        CTTabs tabs = ctppr.addNewTabs();
        CTTabStop tabStop = tabs.addNewTab();
        tabStop.setVal(STTabJc.RIGHT); // 右对齐制表符

        // 根据页面宽度和边距动态计算制表符位置
        long contentWidthTwips = pageWidthTwips - leftMarginTwips - rightMarginTwips;
        tabStop.setPos(BigInteger.valueOf(contentWidthTwips));

        // 添加左侧文本
        XWPFRun leftRun = paragraph.createRun();
        if (leftText == null || leftText.isEmpty()) {
            leftRun.setText("\u00A0");
        } else {
            leftRun.setText(leftText);
        }

        // 添加制表符
        leftRun.addTab();

        // 添加右侧文本
        XWPFRun rightRun = paragraph.createRun();
        if (rightText == null || rightText.isEmpty()) {
            rightRun.setText("\u00A0");
        } else {
            rightRun.setText(rightText);
        }

        // 保存段落引用和运行对象数组
        this.currentParagraph = paragraph;
        this.currentRuns = Arrays.asList(leftRun, rightRun);

        return this;
    }

    /**
     * 设置当前段落的行高。
     * @param value 行高值
     * @param unit 行高单位
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setLineHeight(int value, IndentUnit unit) {
        if (currentParagraph != null) {
            long twips = 0;
            switch (unit) {
                case CM:
                    twips = Math.round(value * 567); // 1cm = 567 twips
                    break;
                case POINTS:
                    twips = value * 20; // 1pt = 20 twips
                    break;
                case TWIPS:
                    twips = value;
                    break;
            }
            // 设置固定行高
            currentParagraph.setSpacingLineRule(LineSpacingRule.EXACT);
            currentParagraph.setSpacingBetween((int) twips);
            // 设置行高为固定值
            CTP ctp = currentParagraph.getCTP();
            CTPPr ctppr = ctp.addNewPPr();
            CTSpacing spacing = ctppr.addNewSpacing();
            spacing.setLine(BigInteger.valueOf(twips));
            spacing.setLineRule(STLineSpacingRule.EXACT);
        }
        return this;
    }

    /**
     * 设置当前文本的下划线样式和颜色。
     * @param pattern 下划线样式 (例如 UnderlinePatterns.SINGLE, UnderlinePatterns.THICK)
     * @param hexColor 颜色值的十六进制字符串 (例如 "FF0000" 为红色)
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setUnderline(UnderlinePatterns pattern, String hexColor) {
        if (currentRuns != null) {
            for (XWPFRun run : currentRuns) {
                run.setUnderline(pattern);
                run.setUnderlineColor(hexColor);
            }
        }
        return this;
    }

    /**
     * 在文档中添加一条指定颜色和粗细的水平线。
     * @param hexColor 颜色值的十六进制字符串 (例如 "FF0000" 为红色)
     * @param thicknessInPoints 线条粗细 (磅)
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator addHorizontalLine(String hexColor, int thicknessInPoints) {
        XWPFParagraph lineParagraph = document.createParagraph();
        // 确保段落属性存在
        CTPPr ppr = lineParagraph.getCTP().isSetPPr() ? lineParagraph.getCTP().getPPr() : lineParagraph.getCTP().addNewPPr();
        // 获取或创建段落的边框属性
        CTPBdr border = ppr.isSetPBdr() ? ppr.getPBdr() : ppr.addNewPBdr();
        // 添加底部边框
        CTBorder bottomBorder = border.addNewBottom();
        // 设置边框颜色
        bottomBorder.setColor(hexColor);
        // 设置边框粗细(单位为八分之一磅)
        bottomBorder.setSz(BigInteger.valueOf(thicknessInPoints * 8));
        // 设置边框类型为单实线
        bottomBorder.setVal(STBorder.SINGLE);

        // 为了确保线条可见,添加一个空的运行(run)
        lineParagraph.createRun();

        // 更新当前段落,以便后续操作可以继续链式调用
        this.currentParagraph = lineParagraph;
        this.currentRuns = new ArrayList<>(); // 重置 currentRuns 为空,因为这条线没有文本
        this.currentRuns.add(lineParagraph.createRun()); // 添加一个空 run,以防后续操作需要一个 run

        return this;
    }

    /**
     * 设置当前段落的缩进(基于字符数)。
     * @param charCount 要缩进的字符数
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setIndentationByChars(int charCount) {
        if (currentParagraph != null) {
            CTP ctp = currentParagraph.getCTP();
            CTPPr ctppr = ctp.addNewPPr();
            CTInd ctInd = ctppr.addNewInd();
            ctInd.setFirstLineChars(BigInteger.valueOf(charCount * 100));
        }
        return this;
    }

    /**
     * TableTextStyle 静态嵌套类,用于封装表格中某个区域的文本样式。
     */
    public static class TableTextStyle {
        private String fontFamily;
        private String fontSize; // 对应 ChineseFontSize 的名称
        private ParagraphAlignment alignment;
        private String backgroundColor;
        private XWPFTableCell.XWPFVertAlign verticalAlignment;
        // 新增单元格边距属性(单位twips)
        private Integer cellMarginTop, cellMarginBottom, cellMarginLeft, cellMarginRight;

        public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment) {
            this(fontFamily, fontSize, alignment, null, null);
        }
        public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment, String backgroundColor) {
            this(fontFamily, fontSize, alignment, backgroundColor, null);
        }
        public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment, String backgroundColor, XWPFTableCell.XWPFVertAlign verticalAlignment,Integer cellMarginLeft, Integer cellMarginRight) {
            this.fontFamily = fontFamily;
            this.fontSize = fontSize;
            this.alignment = alignment;
            this.backgroundColor = backgroundColor;
            this.verticalAlignment = verticalAlignment;
            this.cellMarginLeft = cellMarginLeft;
            this.cellMarginRight = cellMarginRight;
        }
        public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment, String backgroundColor, XWPFTableCell.XWPFVertAlign verticalAlignment) {
            this.fontFamily = fontFamily;
            this.fontSize = fontSize;
            this.alignment = alignment;
            this.backgroundColor = backgroundColor;
            this.verticalAlignment = verticalAlignment;
        }
        public String getFontFamily() { return fontFamily; }
        public String getFontSize() { return fontSize; }
        public ParagraphAlignment getAlignment() { return alignment; }
        public String getBackgroundColor() { return backgroundColor; }
        public XWPFTableCell.XWPFVertAlign getVerticalAlignment() { return verticalAlignment; }
        public Integer getCellMarginTop() { return cellMarginTop; }
        public Integer getCellMarginBottom() { return cellMarginBottom; }
        public Integer getCellMarginLeft() { return cellMarginLeft; }
        public Integer getCellMarginRight() { return cellMarginRight; }
        public TableTextStyle setCellMarginTop(Integer v) { this.cellMarginTop = v; return this; }
        public TableTextStyle setCellMarginBottom(Integer v) { this.cellMarginBottom = v; return this; }
        public TableTextStyle setCellMarginLeft(Integer v) { this.cellMarginLeft = v; return this; }
        public TableTextStyle setCellMarginRight(Integer v) { this.cellMarginRight = v; return this; }
    }

    // 公共实现
    private WordGenerator addTableInternal(
            List<List<String>> data,
            int headerStartRowIndex,
            int headerEndRowIndex,
            TableTextStyle headerStyle,
            TableTextStyle cellStyle,
            List<int[]> mergeRegions,
            XWPFTable.XWPFBorderType borderType,
            int borderWidth,
            String borderColor,
            int defaultRowHeight,
            Map<Integer, Integer> customRowHeights,
            Map<Integer, Integer> customColumnWidths,
            Integer defaultColumnWidth) {
        if (data == null || data.isEmpty()) {
            return this;
        }
        XWPFTable table = document.createTable(data.size(), data.get(0).size());
        CTTblPr tblPr = table.getCTTbl().getTblPr();
        if (tblPr == null) {
            tblPr = table.getCTTbl().addNewTblPr();
        }
        tblPr.addNewTblW().setType(STTblWidth.DXA);
        long contentWidthTwips = pageWidthTwips - leftMarginTwips - rightMarginTwips;
        tblPr.getTblW().setW(BigInteger.valueOf(contentWidthTwips));
        table.setLeftBorder(borderType, borderWidth, 0, borderColor);
        table.setRightBorder(borderType, borderWidth, 0, borderColor);
        table.setTopBorder(borderType, borderWidth, 0, borderColor);
        table.setBottomBorder(borderType, borderWidth, 0, borderColor);
        table.setInsideHBorder(borderType, borderWidth, 0, borderColor);
        table.setInsideVBorder(borderType, borderWidth, 0, borderColor);
        for (int i = 0; i < data.size(); i++) {
            XWPFTableRow row = table.getRow(i);
            int rowHeight = customRowHeights != null && customRowHeights.containsKey(i)
                ? customRowHeights.get(i)
                : defaultRowHeight;
            row.setHeight(rowHeight);
        }
        int totalWidth = (int)contentWidthTwips;
        int totalCustomWidth = 0;
        if (customColumnWidths != null) {
            totalCustomWidth = customColumnWidths.values().stream().mapToInt(Integer::intValue).sum();
        }
        int defaultColumnCount = data.get(0).size() - (customColumnWidths != null ? customColumnWidths.size() : 0);
        int remainingWidth = totalWidth - totalCustomWidth;
        int actualDefaultWidth = defaultColumnWidth != null ? defaultColumnWidth :
            (defaultColumnCount > 0 ? remainingWidth / defaultColumnCount : 0);
        for (int rowIdx = 0; rowIdx < data.size(); rowIdx++) {
            XWPFTableRow row = table.getRow(rowIdx);
            for (int colIdx = 0; colIdx < data.get(0).size(); colIdx++) {
                XWPFTableCell cell = row.getCell(colIdx);
                if (cell != null) {
                    int columnWidth = customColumnWidths != null && customColumnWidths.containsKey(colIdx)
                        ? customColumnWidths.get(colIdx)
                        : actualDefaultWidth;
                    cell.setWidth(String.valueOf(columnWidth));
                }
            }
        }
        for (int i = 0; i < data.size(); i++) {
            List<String> rowData = data.get(i);
            XWPFTableRow row = table.getRow(i);
            while (row.getTableCells().size() < rowData.size()) {
                row.addNewTableCell();
            }
            for (int j = 0; j < rowData.size(); j++) {
                XWPFTableCell cell = row.getCell(j);
                if (cell == null) {
                    cell = row.addNewTableCell();
                }
                clearCell(cell);
                XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0);
                XWPFRun run = paragraph.getRuns().isEmpty() ? paragraph.createRun() : paragraph.getRuns().get(0);
                run.setText(rowData.get(j));
                TableTextStyle styleToApply;
                if (i >= headerStartRowIndex && i <= headerEndRowIndex) {
                    styleToApply = headerStyle;
                } else {
                    styleToApply = cellStyle;
                }
                if (styleToApply != null) {
                    paragraph.setAlignment(styleToApply.getAlignment());
                    run.setFontFamily(styleToApply.getFontFamily());
                    run.setFontSize(ChineseFontSize.getPoints(styleToApply.getFontSize()));
                    if (styleToApply.getVerticalAlignment() != null) {
                        cell.setVerticalAlignment(styleToApply.getVerticalAlignment());
                    }
                    if (styleToApply.getBackgroundColor() != null) {
                        CTTc cttc = cell.getCTTc();
                        CTTcPr tcPr = cttc.isSetTcPr() ? cttc.getTcPr() : cttc.addNewTcPr();
                        CTShd shd = tcPr.isSetShd() ? tcPr.getShd() : tcPr.addNewShd();
                        shd.setVal(STShd.CLEAR);
                        shd.setFill(styleToApply.getBackgroundColor());
                    }
                    // 设置单元格边距
                    CTTc cttc = cell.getCTTc();
                    CTTcPr tcPr = cttc.isSetTcPr() ? cttc.getTcPr() : cttc.addNewTcPr();
                    CTTcMar mar = tcPr.isSetTcMar() ? tcPr.getTcMar() : tcPr.addNewTcMar();
                    if (styleToApply.getCellMarginTop() != null) mar.addNewTop().setW(BigInteger.valueOf(styleToApply.getCellMarginTop()));
                    if (styleToApply.getCellMarginBottom() != null) mar.addNewBottom().setW(BigInteger.valueOf(styleToApply.getCellMarginBottom()));
                    if (styleToApply.getCellMarginLeft() != null) mar.addNewLeft().setW(BigInteger.valueOf(styleToApply.getCellMarginLeft()));
                    if (styleToApply.getCellMarginRight() != null) mar.addNewRight().setW(BigInteger.valueOf(styleToApply.getCellMarginRight()));
                }
            }
        }
        if (mergeRegions != null) {
            for (int[] mergeInfo : mergeRegions) {
                int rowIndex = mergeInfo[0];
                int colIndex = mergeInfo[1];
                int rowSpan = mergeInfo[2];
                int colSpan = mergeInfo[3];
                if (rowSpan > 1) {
                    mergeCellsVertically(table, colIndex, rowIndex, rowIndex + rowSpan - 1);
                }
                if (colSpan > 1) {
                    mergeCellsHorizontally(table, rowIndex, colIndex, colIndex + colSpan - 1);
                }
            }
        }
        return this;
    }

    /**
     * 页眉页脚配置类。
     */
    public static class HeaderFooterConfig {
        private String headerText;
        private String footerText;
        private String fontFamily = "宋体";
        private int fontSize = 14; // 四号
        private ParagraphAlignment alignment = ParagraphAlignment.CENTER;
        // 可扩展更多属性

        public HeaderFooterConfig setHeaderText(String headerText) { this.headerText = headerText; return this; }
        public HeaderFooterConfig setFooterText(String footerText) { this.footerText = footerText; return this; }
        public HeaderFooterConfig setFontFamily(String fontFamily) { this.fontFamily = fontFamily; return this; }
        public HeaderFooterConfig setFontSize(int fontSize) { this.fontSize = fontSize; return this; }
        public HeaderFooterConfig setAlignment(ParagraphAlignment alignment) { this.alignment = alignment; return this; }
        public String getHeaderText() { return headerText; }
        public String getFooterText() { return footerText; }
        public String getFontFamily() { return fontFamily; }
        public int getFontSize() { return fontSize; }
        public ParagraphAlignment getAlignment() { return alignment; }
    }

    /**
     * 设置页眉页脚,支持页码格式如 - {PAGE} -,宋体四号,居中。
     * @param config 页眉页脚配置
     * @return WordGenerator 实例,支持链式调用
     */
    public WordGenerator setHeaderFooter(HeaderFooterConfig config) {
        CTSectPr sectPr = document.getDocument().getBody().isSetSectPr()
                ? document.getDocument().getBody().getSectPr()
                : document.getDocument().getBody().addNewSectPr();
        XWPFHeaderFooterPolicy policy = new XWPFHeaderFooterPolicy(document, sectPr);
        // 页眉
        if (config.getHeaderText() != null && !config.getHeaderText().isEmpty()) {
            XWPFHeader header = policy.createHeader(XWPFHeaderFooterPolicy.DEFAULT);
            XWPFParagraph para = header.createParagraph();
            para.setAlignment(config.getAlignment());
            XWPFRun run = para.createRun();
            run.setFontFamily(config.getFontFamily());
            run.setFontSize(config.getFontSize());
            run.setText(config.getHeaderText());
        }
        // 页脚
        if (config.getFooterText() != null && !config.getFooterText().isEmpty()) {
            XWPFFooter footer = policy.createFooter(XWPFHeaderFooterPolicy.DEFAULT);
            XWPFParagraph para = footer.createParagraph();
            para.setAlignment(config.getAlignment());
            XWPFRun run = para.createRun();
            run.setFontFamily(config.getFontFamily());
            run.setFontSize(config.getFontSize());
            String text = config.getFooterText();
            int pageIdx = text.indexOf("{PAGE}");
            if (pageIdx >= 0) {
                // 前缀
                if (pageIdx > 0) run.setText(text.substring(0, pageIdx));
                // 页码域
                run.getCTR().addNewFldChar().setFldCharType(STFldCharType.BEGIN);
                run = para.createRun();
                run.setFontFamily(config.getFontFamily());
                run.setFontSize(config.getFontSize());
                run.getCTR().addNewInstrText().setStringValue(" PAGE ");
                run = para.createRun();
                run.setFontFamily(config.getFontFamily());
                run.setFontSize(config.getFontSize());
                run.getCTR().addNewFldChar().setFldCharType(STFldCharType.END);
                // 后缀
                if (pageIdx + 6 < text.length()) {
                    run = para.createRun();
                    run.setFontFamily(config.getFontFamily());
                    run.setFontSize(config.getFontSize());
                    run.setText(text.substring(pageIdx + 6));
                }
            } else {
                run.setText(text);
            }
        }
        return this;
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到