You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用iTextPDF库替换PDF中的${placeholder}占位符文本?

用iText7替换PDF中的${placeholder}占位符解决方案

我懂你这种卡壳的感觉——照着官方示例操作却没效果确实让人头疼。其实你提到的那个官方示例是针对替换PDF底层对象(比如图片、字体这类资源)的,和你要的文本占位符替换完全不是一个场景,没效果太正常了。

PDF里的文本不像Word那样是规整的“占位符块”,它通常是由多个零散的文本片段组成的,所以要实现占位符替换,核心思路是遍历页面文本、定位占位符位置、覆盖原文本并写入新内容。下面给你一套可行的实现方案:

核心实现步骤

  1. 监听PDF页面的文本渲染事件,收集所有文本片段的内容和位置信息
  2. 筛选出包含目标占位符的文本片段
  3. 用白色矩形覆盖原占位符区域(避免残留)
  4. 在原位置写入替换后的文本,保持字体、字号、位置和原占位符一致

Java代码示例

基础版(占位符未被拆分)

如果你的占位符是作为一个完整的文本片段存在的,用这个版本就足够:

import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.parser.EventType;
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData;
import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo;
import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class PlaceholderReplacer {
    public static void replacePlaceholder(String srcPath, String destPath, String placeholder, String replacement) throws IOException {
        // 打开PDF文档(读模式+写模式)
        try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcPath), new PdfWriter(destPath))) {
            // 遍历每一页
            for (int pageNum = 1; pageNum <= pdfDoc.getNumberOfPages(); pageNum++) {
                PdfPage page = pdfDoc.getPage(pageNum);
                TextSearchListener listener = new TextSearchListener(placeholder);
                PdfCanvasProcessor processor = new PdfCanvasProcessor(listener);
                processor.processPageContent(page);

                // 处理找到的所有匹配占位符
                for (TextRenderInfo textInfo : listener.getMatchingText()) {
                    Rectangle placeholderRect = textInfo.getBaseline().getBoundingRectangle();
                    // 1. 用白色矩形覆盖原占位符(消除原文本)
                    page.newContentStreamBefore().saveState()
                            .setFillColorGray(1f)
                            .rectangle(placeholderRect.getLeft(), placeholderRect.getBottom(), placeholderRect.getWidth(), placeholderRect.getHeight())
                            .fill()
                            .restoreState();

                    // 2. 在原位置写入替换文本,保持原字体、字号和位置
                    page.newContentStreamBefore().beginText()
                            .setFontAndSize(textInfo.getFont(), textInfo.getFontSize())
                            .setTextMatrix(textInfo.getTextMatrix().get(0, 0), textInfo.getTextMatrix().get(1, 0),
                                    textInfo.getTextMatrix().get(0, 1), textInfo.getTextMatrix().get(1, 1),
                                    textInfo.getTextMatrix().get(0, 2), textInfo.getTextMatrix().get(1, 2))
                            .showText(replacement)
                            .endText();
                }
            }
        }
    }

    // 自定义监听器:捕获包含目标占位符的文本片段
    private static class TextSearchListener implements IEventListener {
        private final String targetPlaceholder;
        private final List<TextRenderInfo> matchingTextList = new ArrayList<>();

        public TextSearchListener(String placeholder) {
            this.targetPlaceholder = placeholder;
        }

        @Override
        public void eventOccurred(IEventData data, EventType type) {
            if (type == EventType.RENDER_TEXT) {
                TextRenderInfo textInfo = (TextRenderInfo) data;
                String currentText = textInfo.getText();
                if (currentText.contains(targetPlaceholder)) {
                    matchingTextList.add(textInfo);
                }
            }
        }

        @Override
        public List<EventType> getSupportedEvents() {
            return List.of(EventType.RENDER_TEXT);
        }

        public List<TextRenderInfo> getMatchingText() {
            return matchingTextList;
        }
    }

    // 测试入口
    public static void main(String[] args) throws IOException {
        replacePlaceholder("input.pdf", "output.pdf", "${username}", "Jane Smith");
    }
}

进阶版(处理被拆分的占位符)

有时候PDF生成工具会把一个完整的占位符拆成多个文本片段(比如${user和name}),这时候需要把相邻的文本片段合并后再匹配,修改监听器即可:

// 修改后的TextSearchListener,支持合并相邻文本片段
private static class TextSearchListener implements IEventListener {
    private final String targetPlaceholder;
    private final List<TextRenderInfo> matchingTextList = new ArrayList<>();
    private List<TextRenderInfo> currentTextBlock = new ArrayList<>();
    private float lastTextEndX = -1;

    public TextSearchListener(String placeholder) {
        this.targetPlaceholder = placeholder;
    }

    @Override
    public void eventOccurred(IEventData data, EventType type) {
        if (type == EventType.RENDER_TEXT) {
            TextRenderInfo textInfo = (TextRenderInfo) data;
            float currentTextStartX = textInfo.getBaseline().getStartPoint().get(0);

            // 判断当前文本是否和上一个片段相邻(阈值1f可根据实际调整)
            if (currentTextBlock.isEmpty() || Math.abs(currentTextStartX - lastTextEndX) < 1f) {
                currentTextBlock.add(textInfo);
            } else {
                // 检查当前文本块是否包含占位符
                checkTextBlock(currentTextBlock);
                currentTextBlock.clear();
                currentTextBlock.add(textInfo);
            }

            lastTextEndX = textInfo.getBaseline().getEndPoint().get(0);
        }
    }

    @Override
    public List<EventType> getSupportedEvents() {
        return List.of(EventType.RENDER_TEXT);
    }

    public List<TextRenderInfo> getMatchingText() {
        // 处理最后一个未检查的文本块
        checkTextBlock(currentTextBlock);
        return matchingTextList;
    }

    // 检查文本块拼接后是否包含占位符
    private void checkTextBlock(List<TextRenderInfo> textBlock) {
        StringBuilder blockText = new StringBuilder();
        for (TextRenderInfo info : textBlock) {
            blockText.append(info.getText());
        }
        if (blockText.toString().contains(targetPlaceholder)) {
            matchingTextList.addAll(textBlock);
        }
    }
}

注意事项

  • 如果你需要替换多个不同的占位符,只需多次调用replacePlaceholder方法即可
  • 如果替换后的文本长度和原占位符差异较大,可能会导致排版错乱,这时候需要更复杂的布局调整逻辑(比如重新计算文本位置)
  • 确保你使用的iText7版本是最新稳定版,避免API兼容性问题

内容的提问来源于stack exchange,提问作者Tymur Berezhnoi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:10:58