求助:使用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
相关产品推荐
相关产品推荐

