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

如何自定义RegexBasedLocationExtractionStrategy以同时返回字体信息与文本位置

解决方案与建议

一、当前问题的可行实现方式

1. 自定义带字体信息的CharacterRenderInfo与IPdfTextLocation

你已经实现的MyCharacterRenderInfo用来保存TextRenderInfo中的字体信息(如FontProgram、字号、字体样式等)是可行的。接下来需要自定义一个IPdfTextLocation的实现类,比如StyledPdfTextLocation,除了位置矩形,还要封装字体相关属性:

public class StyledPdfTextLocation implements IPdfTextLocation {
    private final Rectangle rectangle;
    private final FontProgram fontProgram;
    private final float fontSize;
    // 可按需添加其他字体属性,如加粗、斜体等

    public StyledPdfTextLocation(Rectangle rectangle, FontProgram fontProgram, float fontSize) {
        this.rectangle = rectangle;
        this.fontProgram = fontProgram;
        this.fontSize = fontSize;
    }

    @Override
    public Rectangle getRectangle() {
        return rectangle;
    }

    // 字体信息的getter方法
    public FontProgram getFontProgram() { return fontProgram; }
    public float getFontSize() { return fontSize; }
}

2. 重写getResultantLocations()替代toRectangles()

不要直接重写toRectangles()(避免复制长代码和访问私有方法的问题),而是重写RegexBasedLocationExtractionStrategy的getResultantLocations()方法。这个公开方法负责将匹配到的CharacterRenderInfo转换为最终结果,你可以在这里自行处理分组和合并逻辑:

public class StyledRegexExtractionStrategy extends RegexBasedLocationExtractionStrategy {
    private final Pattern pattern;

    public StyledRegexExtractionStrategy(Pattern pattern) {
        super(pattern);
        this.pattern = pattern;
    }

    @Override
    protected CharacterRenderInfo toCRI(TextRenderInfo renderInfo) {
        // 返回自定义的MyCharacterRenderInfo,携带字体信息
        return new MyCharacterRenderInfo(renderInfo);
    }

    @Override
    public List<IPdfTextLocation> getResultantLocations() {
        List<IPdfTextLocation> result = new ArrayList<>();
        List<CharacterRenderInfo> matchedCRIs = getMatchedCharacterRenderInfos();

        if (matchedCRIs.isEmpty()) {
            return result;
        }

        // 按行分组:同一行文本的y坐标基本一致,允许微小误差(如1f)
        List<List<MyCharacterRenderInfo>> lineGroups = new ArrayList<>();
        List<MyCharacterRenderInfo> currentGroup = new ArrayList<>();
        currentGroup.add((MyCharacterRenderInfo) matchedCRIs.get(0));

        for (int i = 1; i < matchedCRIs.size(); i++) {
            MyCharacterRenderInfo current = (MyCharacterRenderInfo) matchedCRIs.get(i);
            MyCharacterRenderInfo prev = currentGroup.get(currentGroup.size() - 1);
            // 比较基线y坐标判断是否同行,可根据实际情况调整误差阈值
            boolean sameLine = Math.abs(current.getBaseline().getStartPoint().getY() - prev.getBaseline().getStartPoint().getY()) < 1f;

            if (sameLine) {
                currentGroup.add(current);
            } else {
                lineGroups.add(currentGroup);
                currentGroup = new ArrayList<>();
                currentGroup.add(current);
            }
        }
        lineGroups.add(currentGroup);

        // 处理每个行组,生成带字体信息的StyledPdfTextLocation
        for (List<MyCharacterRenderInfo> group : lineGroups) {
            // 合并当前组的矩形
            float left = Float.MAX_VALUE;
            float right = Float.MIN_VALUE;
            float top = Float.MIN_VALUE;
            float bottom = Float.MAX_VALUE;
            // 默认取第一个字符的字体信息,若标签内字体统一则足够;若有变化可按需处理
            MyCharacterRenderInfo firstCri = group.get(0);
            FontProgram font = firstCri.getFontProgram();
            float fontSize = firstCri.getFontSize();

            for (MyCharacterRenderInfo cri : group) {
                Rectangle rect = cri.getBoundingBox();
                left = Math.min(left, rect.getLeft());
                right = Math.max(right, rect.getRight());
                top = Math.max(top, rect.getTop());
                bottom = Math.min(bottom, rect.getBottom());
            }

            Rectangle mergedRect = new Rectangle(left, bottom, right - left, top - bottom);
            result.add(new StyledPdfTextLocation(mergedRect, font, fontSize));
        }

        return result;
    }
}

3. 多行匹配的处理细节

当使用Pattern.DOTALL匹配多行文本时,上述分组逻辑会自动将同一行的字符归为一组,不同行的分为不同组,最终每个跨多行的匹配块会拆分为多个行级的StyledPdfTextLocation,你可以根据需求选择合并或分别处理这些行级位置。

二、关于API改进的疑问

你提出的拆分可重写方法的思路非常合理:如果RegexBasedLocationExtractionStrategy能提供两个独立的扩展点——一个负责从TextRenderInfo生成CharacterRenderInfo(已有toCRI()),另一个负责从CharacterRenderInfo集合生成/合并IPdfTextLocation,确实能大幅降低扩展难度,避免开发者因访问私有方法或复制长代码而陷入麻烦。这种设计能让扩展逻辑更聚焦于业务需求(如保留字体、颜色等样式信息),无需关注内部的文本分组、矩形合并细节。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 09:54:52