PAYATHON 2026

如何让 RegexBasedLocationExtractionStrategy 同时返回字体信息?

数据小陈

可以重写 toCRI() 保存字符的字体信息,再在 toRectangles() 中调用 super.toRectangles(cris) 计算位置,并生成自定义匹配结果。这样可以复用原有的跨行分组逻辑,无须复制整个方法,也无须直接访问 TextChunk::sameLine()。

一次正则匹配可能跨越多行,也可能涉及多种字体。设计结果数据结构时,应允许一个匹配对应多个矩形和多种字体。

为什么字体信息没有自动进入最终结果?

toCRI() 和 toRectangles() 提供了读取、保存额外信息的机会,但不会自动扩展结果接口。以 iText 9.5.0 的 API 为例,IPdfTextLocation 只定义了 getRectangle() 和 getText(),没有字体属性。需要将字体信息放进自定义结果对象,或通过额外的查询方法返回。IPdfTextLocation API

所核对的 develop 分支实现会把一次匹配涉及的 CharacterRenderInfo 列表传入 toRectangles(),再根据返回的矩形创建默认位置对象。可以利用这个扩展点保存一次匹配的矩形列表和字符信息,继续沿用原有的矩形计算算法。项目未提供具体版本,使用前应核对所依赖版本的调用流程。RegexBasedLocationExtractionStrategy 源码

示例:另外返回包含字体的匹配结果

下面的 Java 示例保留每次匹配的所有矩形,以及参与匹配的字符、字体和字号。它通过独立的 getFontMatches() 方法返回这些信息,不改变 IPdfTextLocation 接口。

import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo;
import com.itextpdf.kernel.pdf.canvas.parser.listener.CharacterRenderInfo;
import com.itextpdf.kernel.pdf.canvas.parser.listener.RegexBasedLocationExtractionStrategy;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class FontAwareStrategy
        extends RegexBasedLocationExtractionStrategy {

    private final List<FontMatch> matches = new ArrayList<>();

    public FontAwareStrategy(Pattern pattern) {
        super(pattern);
    }

    public static class FontCharacterInfo extends CharacterRenderInfo {
        public final PdfFont font;
        public final float fontSize;

        public FontCharacterInfo(TextRenderInfo info) {
            super(info);
            font = info.getFont();
            fontSize = info.getFontSize();
        }
    }

    public static class FontMatch {
        public final List<Rectangle> rectangles;
        public final List<FontCharacterInfo> characters;

        private FontMatch(List<Rectangle> rectangles,
                          List<FontCharacterInfo> characters) {
            this.rectangles = new ArrayList<>(rectangles);
            this.characters = new ArrayList<>(characters);
        }
    }

    @Override
    protected List<CharacterRenderInfo> toCRI(TextRenderInfo info) {
        List<CharacterRenderInfo> result = new ArrayList<>();
        info.getCharacterRenderInfos().forEach(character ->
                result.add(new FontCharacterInfo(character)));
        return result;
    }

    @Override
    protected List<Rectangle> toRectangles(
            List<CharacterRenderInfo> characters) {
        List<Rectangle> rectangles = super.toRectangles(characters);
        List<FontCharacterInfo> styledCharacters = new ArrayList<>();

        for (CharacterRenderInfo character : characters) {
            styledCharacters.add((FontCharacterInfo) character);
        }

        matches.add(new FontMatch(rectangles, styledCharacters));
        return rectangles;
    }

    public List<FontMatch> getFontMatches() {
        matches.clear();
        super.getResultantLocations();
        return new ArrayList<>(matches);
    }
}

示例在创建字符对象时读取字体和字号,无须为了稍后访问它们而缓存整个 TextRenderInfo。getCharacterRenderInfos() 提供逐字形的渲染信息,getFont() 和 getFontSize() 提供相应字体属性。字形与 Java 字符并不保证一一对应。TextRenderInfo API

处理单页后,可以这样获取结果:

FontAwareStrategy strategy = new FontAwareStrategy(
        Pattern.compile(Pattern.quote("$key1")));

new com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor(strategy)
        .processPageContent(page);

List<FontAwareStrategy.FontMatch> matches = strategy.getFontMatches();

这里用 Pattern.quote("$key1") 匹配字面标签,避免 $ 被解释为正则表达式中的行尾锚点。

跨行匹配和混合字体怎么处理?

示例中的 FontMatch 对应一次匹配,rectangles 保存该匹配的多个位置区域,characters 保存对应的字符信息。两个列表不是按下标一一对应的。

如果标签内的字体和字号一致,可以验证后选择其中一个字符的样式,用于绘制替换文本。跨行时也可以继续使用这套样式,同时保留多个矩形。

如果标签包含混合字体,应明确替换规则:统一采用某个字符的字体,或者按连续样式片段分别绘制。如果要求每个行片段直接携带精确的样式范围,就需要进一步保留“行片段到字符集合”的映射;仅保存最终矩形无法完整表达这种关系。

Pattern.DOTALL 只影响正则表达式中 . 对换行符的匹配行为。它不会改变 PDF 的阅读顺序,也不会为跨行文字确定统一字体。

使用时需要注意什么?

  • 示例在 toRectangles() 中记录每次匹配,所核对的实现随后还会对标准位置结果排序和去重。因此,不能认为 getFontMatches() 的条目数量或顺序与最终 getResultantLocations() 完全一致。
  • 每页使用新的策略实例。该策略会保存页面内的字符位置,官方 API 明确要求处理多页时为每页创建新实例。RegexBasedLocationExtractionStrategy API
  • 字体相同也不代表任意替换文本都能显示。替换前需要确认字体包含新文本所需的字形,尤其是 PDF 使用字体子集时。
  • 位置矩形不足以完整恢复排版。如果模板存在旋转、缩放或特殊字符间距,还需要保存基线、变换和间距等信息。不要直接把矩形左下角当作文字基线。
  • 覆盖框只改变可见效果,原标签通常仍在 PDF 内容流中。如果要求搜索、复制或提取时也找不到标签,需要另外处理原有内容。

更细粒度的扩展方法是否有帮助?

有帮助。单字符工厂方法可以减少 toCRI() 中重复的遍历代码;接收字符范围并创建结果对象的方法,则能更直接地保留匹配分组、行片段和字体信息。

这属于 API 设计建议,不能据此假定当前版本已经提供这些方法。使用现有扩展点时,可以先通过 super.toRectangles(cris) 复用位置计算,再返回自定义匹配对象,通常就能满足模板标签定位和字体保留的需求。

备注:内容仅供参考。