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

求助:使用Apache PDFBox替换PDF中非注解旧域名链接

解决方案:PDF纯文本链接批量替换(保留图片)

核心问题分析

你的PDF链接为纯文本形式(非注解式可点击链接),因此解析注解的方法无法捕获;原PDFTextStripper仅提取文本但无法修改PDF内容,同时存在跨换行/带空格的链接识别难题,需确保图片资源完整保留。

可行方案步骤

1. 精准提取带位置的URL

通过重写PDFTextStripper的writeString方法,捕获每个文本片段的坐标位置,同时优化正则表达式识别带换行/空格的URL。

2. 覆盖原文本并替换域名

使用PDPageContentStream以覆盖模式绘制白色矩形遮盖原URL,再写入替换后的新域名链接,可选将纯文本转换为可点击注解链接。

3. 确保图片不丢失

PDFBox加载和保存文档时默认保留页面资源,无需额外处理即可保证图片完整。

代码实现

自定义URL提取器(含位置捕获)

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class UrlExtractingStripper extends PDFTextStripper {
    private final List<UrlTextPosition> urlPositions = new ArrayList<>();
    // 匹配带换行/空格的URL,终止于空白、标点或结尾
    private final Pattern urlPattern = Pattern.compile("(?:https?://|www\\.)(?:[\\S]|\\s*\\n\\s*)+?(?=\\s|$|\\p{Punct})");

    public UrlExtractingStripper() throws IOException {
        super();
    }

    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        Matcher matcher = urlPattern.matcher(text);
        while (matcher.find()) {
            String rawUrl = matcher.group();
            String cleanUrl = rawUrl.replaceAll("\\s+|\\n+", ""); // 清理换行和空格
            // 定位URL对应的文本片段位置
            int startIdx = matcher.start();
            int endIdx = matcher.end();
            TextPosition startPos = textPositions.get(startIdx);
            TextPosition endPos = textPositions.get(endIdx - 1);
            urlPositions.add(new UrlTextPosition(cleanUrl, startPos, endPos));
        }
        super.writeString(text, textPositions);
    }

    public List<UrlTextPosition> getUrlPositions() {
        return urlPositions;
    }

    public static class UrlTextPosition {
        public final String url;
        public final TextPosition start;
        public final TextPosition end;

        public UrlTextPosition(String url, TextPosition start, TextPosition end) {
            this.url = url;
            this.start = start;
            this.end = end;
        }
    }
}

主替换逻辑

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;

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

public class PdfUrlReplacer {
    // 替换配置:旧域名→新域名
    private static final String OLD_DOMAIN = "old-domain.com";
    private static final String NEW_DOMAIN = "new-domain.com";

    public static void replaceUrlsInPdf(String inputPath, String outputPath) throws IOException {
        try (PDDocument document = PDDocument.load(new File(inputPath))) {
            for (PDPage page : document.getPages()) {
                // 提取当前页面的URL及位置信息
                UrlExtractingStripper stripper = new UrlExtractingStripper();
                int pageNum = document.getPages().indexOf(page) + 1;
                stripper.setStartPage(pageNum);
                stripper.setEndPage(pageNum);
                stripper.getText(document);
                List<UrlExtractingStripper.UrlTextPosition> urlPositions = stripper.getUrlPositions();

                // 创建内容流,覆盖原URL文本
                try (PDPageContentStream contentStream = new PDPageContentStream(
                        document, page, PDPageContentStream.AppendMode.OVERLAY, true, true)) {

                    for (UrlExtractingStripper.UrlTextPosition pos : urlPositions) {
                        if (pos.url.contains(OLD_DOMAIN)) {
                            String newUrl = pos.url.replace(OLD_DOMAIN, NEW_DOMAIN);
                            PDFont font = pos.start.getFont(); // 使用原文本字体保证样式一致
                            float fontSize = pos.start.getFontSize();

                            // 计算原URL的覆盖范围
                            float x = pos.start.getX();
                            float y = pos.start.getY();
                            float width = pos.end.getX() - pos.start.getX() + pos.end.getWidth();
                            float height = fontSize * 1.2f; // 适当放大保证完全覆盖

                            // 绘制白色矩形遮盖原URL
                            contentStream.setNonStrokingColor(255, 255, 255);
                            contentStream.addRect(x, y - height/2, width, height);
                            contentStream.fill();

                            // 写入替换后的新URL(蓝色模拟链接样式)
                            contentStream.setNonStrokingColor(0, 0, 255);
                            contentStream.setFont(font, fontSize);
                            contentStream.beginText();
                            contentStream.newLineAtOffset(x, y);
                            contentStream.showText(newUrl);
                            contentStream.endText();

                            // 可选:将纯文本转换为可点击注解链接
                            /*
                            PDAnnotationLink link = new PDAnnotationLink();
                            PDActionURI action = new PDActionURI();
                            action.setURI(newUrl);
                            link.setAction(action);
                            PDRectangle rect = new PDRectangle(x, y - height/2, width, height);
                            link.setRectangle(rect);
                            page.getAnnotations().add(link);
                            */
                        }
                    }
                }
            }
            // 保存修改后的PDF,图片自动保留
            document.save(outputPath);
        }
    }

    public static void main(String[] args) throws IOException {
        replaceUrlsInPdf("input.pdf", "output.pdf");
    }
}

Maven依赖(保持原配置)

<dependency>
   <groupId>org.apache.pdfbox</groupId>
   <artifactId>pdfbox</artifactId>
   <version>${pdfbox.version}</version>
</dependency>

关键注意事项

  • 字体匹配:代码中直接使用原文本的字体(pos.start.getFont()),避免替换后样式不一致。
  • URL识别:正则表达式已处理跨换行、带空格的URL,提取后自动清理空白字符。
  • 图片保留:PDFBox默认保留页面所有资源,无需额外操作即可保证图片完整。
  • 可点击链接:若需要将纯文本转为可点击链接,取消代码中对应注释块即可。

内容的提问来源于stack exchange,提问作者Yogesh Rathi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:24:58