如何自定义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

