一般流程和一个PDFBox问题
理论上,应该从PDFTextStripper 派生一个类并覆盖其方法开始:
/**
* Write a Java string to the output stream. The default implementation will ignore the <code>textPositions</code>
* and just calls {@link #writeString(String)}.
*
* @param text The text to write to the stream.
* @param textPositions The TextPositions belonging to the text.
* @throws IOException If there is an error when writing the text.
*/
protected void writeString(String text, List<TextPosition> textPositions) throws IOException
{
writeString(text);
}
然后您的覆盖应该使用List<TextPosition> textPositions 而不是String text;每个TextPosition 本质上代表一个单独的字母,并且在绘制该字母时,图形状态的信息处于活动状态。
很遗憾,textPositions 列表不包含当前版本 1.8.3 中的正确内容。例如。对于“这是普通文本”这一行。在您的 PDF 中,方法 writeString 被调用四次,对字符串“This”、“is”、“normal”和“text”各调用一次。不幸的是,textPositions 列表每次都包含 TextPosition 最后一个字符串“text”的字母的实例。
这实际上已被证明已被识别为 PDFBox 问题PDFBOX-1804,同时已在 1.8.4 和 2.0.0 版本中解决。
这就是说,一旦你有一个固定的PDFBox版本,你可以检查一些人工样式如下:
人工斜体
这个文本样式在页面内容中是这样创建的:
BT
/F0 1 Tf
24 0 5.10137 24 66 695.5877 Tm
0 Tr
[<03>]TJ
...
相关部分发生在设置文本矩阵Tm。 5.10137 是文本被剪切的一个因素。
当您检查TextPosition textPosition 如上所述时,您可以使用查询此值
textPosition.getTextPos().getValue(1, 0)
如果此值大于 0.0,则您有人工斜体。如果它小于 0.0,则您有人为的反向斜体。
人工粗体或轮廓文本
这些人工样式使用不同渲染模式的双打印字母;例如大写的“T”,如果是粗体:
0 0 0 1 k
...
BT
/F0 1 Tf
24 0 0 24 66.36 729.86 Tm
<03>Tj
4 M 0.72 w
0 0 Td
1 Tr
0 0 0 1 K
<03>Tj
ET
(即先用普通模式绘制字母,填充字母区域,然后用轮廓模式绘制,沿着字母边框画一条线,都是黑色,CMYK 0,0,0,1;这样就剩下了厚字的印象。)
如果是大纲:
BT
/F0 1 Tf
24 0 0 24 66 661.75 Tm
0 0 0 0 k
<03>Tj
/GS1 gs
4 M 0.288 w
0 0 Td
1 Tr
0 0 0 1 K
<03>Tj
ET
(即先把字母画成普通模式白色,CMYK 0,0,0,0,填充字母区域,然后用轮廓模式画,沿着字母边框画一条线,黑色,CMYK 0, 0, 0, 1;这会留下白底黑字的印象。)
很遗憾,PDFBox PDFTextStripper 不跟踪文本呈现模式。此外,它明确地将重复的字符出现在大致相同的位置。因此,识别这些人为风格的任务无法完成。
如果您真的需要这样做,您必须更改 TextPosition 以包含渲染模式,PDFStreamEngine 将其添加到生成的 TextPosition 实例中,并将 PDFTextStripper 更改为 not 在processTextPosition 中删除重复的字形。
更正
我写的
很遗憾,PDFBox PDFTextStripper 不跟踪文本呈现模式。
这并不完全正确,您可以使用getGraphicsState().getTextState().getRenderingMode() 找到当前 渲染模式。这意味着在processTextPosition 期间,您确实有可用的渲染模式,并且可以尝试在某处存储给定TextPosition 的渲染模式(和颜色!)信息,例如在一些Map<TextPosition, ...>,供以后使用。
此外,它还显式删除了在大致相同位置出现的重复字符。
您可以通过调用 setSuppressDuplicateOverlappingText(false) 来禁用此功能。
通过这两项更改,您应该也可以进行检查人工粗体和轮廓所需的测试。
如果您在processTextPosition 的早期存储和检查样式,甚至可能不需要后一种更改。
如何获取渲染模式和颜色
正如更正中所述,确实可以通过在processTextPosition 覆盖中收集这些信息来检索渲染模式和颜色信息。
对此,OP 评论说
描边和非描边的颜色总是黑色
起初这有点令人惊讶,但在查看PDFTextStripper.properties(从中初始化文本提取期间支持的运算符)后,原因就清楚了:
# The following operators are not relevant to text extraction,
# so we can silently ignore them.
...
K
k
因此颜色设置操作符(尤其是本文档中用于 CMYK 颜色的操作符)在此上下文中被忽略!幸运的是,PageDrawer 的这些运算符的实现也可以在这种情况下使用。
因此,以下概念验证展示了如何检索所有必需的信息。
public class TextWithStateStripperSimple extends PDFTextStripper
{
public TextWithStateStripperSimple() throws IOException {
super();
setSuppressDuplicateOverlappingText(false);
registerOperatorProcessor("K", new org.apache.pdfbox.util.operator.SetStrokingCMYKColor());
registerOperatorProcessor("k", new org.apache.pdfbox.util.operator.SetNonStrokingCMYKColor());
}
@Override
protected void processTextPosition(TextPosition text)
{
renderingMode.put(text, getGraphicsState().getTextState().getRenderingMode());
strokingColor.put(text, getGraphicsState().getStrokingColor());
nonStrokingColor.put(text, getGraphicsState().getNonStrokingColor());
super.processTextPosition(text);
}
Map<TextPosition, Integer> renderingMode = new HashMap<TextPosition, Integer>();
Map<TextPosition, PDColorState> strokingColor = new HashMap<TextPosition, PDColorState>();
Map<TextPosition, PDColorState> nonStrokingColor = new HashMap<TextPosition, PDColorState>();
protected void writeString(String text, List<TextPosition> textPositions) throws IOException
{
writeString(text + '\n');
for (TextPosition textPosition: textPositions)
{
StringBuilder textBuilder = new StringBuilder();
textBuilder.append(textPosition.getCharacter())
.append(" - shear by ")
.append(textPosition.getTextPos().getValue(1, 0))
.append(" - ")
.append(textPosition.getX())
.append(" ")
.append(textPosition.getY())
.append(" - ")
.append(renderingMode.get(textPosition))
.append(" - ")
.append(toString(strokingColor.get(textPosition)))
.append(" - ")
.append(toString(nonStrokingColor.get(textPosition)))
.append('\n');
writeString(textBuilder.toString());
}
}
String toString(PDColorState colorState)
{
if (colorState == null)
return "null";
StringBuilder builder = new StringBuilder();
for (float f: colorState.getColorSpaceValue())
{
builder.append(' ')
.append(f);
}
return builder.toString();
}
}
使用这个你得到句号'.'在普通文本中为:
. - shear by 0.0 - 256.5701 88.6875 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
你会得到人工粗体字;
. - shear by 0.0 - 378.86 122.140015 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
. - shear by 0.0 - 378.86002 122.140015 - 1 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
人工斜体:
. - shear by 5.10137 - 327.121 156.4123 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
并且在人工轮廓中:
. - shear by 0.0 - 357.25 190.25 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 0.0
. - shear by 0.0 - 357.25 190.25 - 1 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 0.0
那么,识别这些人工样式所需的所有信息都已完成。现在您只需分析数据即可。
顺便说一句,看看人工加粗的情况:坐标可能并不总是相同,而只是非常相似。因此,测试两个文本位置对象是否描述相同的位置需要一些宽大处理。