【问题标题】:How to determine artificial bold style ,artificial italic style and artificial outline style of a text using PDFBOX如何使用PDFBOX确定文本的人工粗体样式、人工斜体样式和人工轮廓样式
【发布时间】:2014-01-19 14:51:15
【问题描述】:

我正在使用 PDFBox 来验证 pdf 文档。 检查 PDF 中存在的以下类型的文本有一定的要求

  • 人工粗体样式文本
  • 人工斜体样式文本。
  • 人工轮廓样式文本

我在 PDFBOX api 列表中进行了搜索,但找不到此类 api。

谁能帮助我,告诉我如何使用 PDFBOX 确定要在 PDF 中出现的不同类型的人工字体/文本样式。

【问题讨论】:

  • 我似乎没有访问您的示例文档的权限。话虽如此,有多种方法可以例如加粗一些输出。你想找到尽可能多的方法吗?还是只是一种特定的方式?
  • 抱歉将 PDF 设为私有内容。不过,我已经将示例 PDF 设为公开。如果您能展示进行这些检查的最佳方法,那就太好了......谢谢前进..
  • 我已开始查看您的示例文档。人工斜体样式已经准备好,只是文本矩阵的一些特殊效果。但是,人工粗体非常冗长。我认为这是不必要的,但也许有某些设备需要这样做。我稍后会回来。
  • 嗨,mkl,你有机会调查这个问题吗?
  • 是的,我对 PDFBox 的行为感到有些惊讶。我正在进一步研究。

标签: pdf font-size detect pdfbox


【解决方案1】:

一般流程和一个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&lt;TextPosition&gt; 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&lt;TextPosition, ...&gt;,供以后使用。

此外,它还显式删除了在大致相同位置出现的重复字符。

您可以通过调用 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

那么,识别这些人工样式所需的所有信息都已完成。现在您只需分析数据即可。

顺便说一句,看看人工加粗的情况:坐标可能并不总是相同,而只是非常相似。因此,测试两个文本位置对象是否描述相同的位置需要一些宽大处理。

【讨论】:

  • 谢谢。这是否意味着我需要下载已修复文本剥离器问题的固定 PDFBOX 版本?对于大型 PDF,是否每次都为每个文本调用 PDfTextStripper 函数?
  • 另外我目前正在使用 PDFBOX-1.7.1.jar 我找不到 TextStripper 类具有方法 writeString(String text, List textPositions)。它只有一种方法称为 writeString(String文本)。
  • 去年 11 月添加了包含 TextPosition 实例列表的方法重载,以允许提取额外的样式信息。因此,它在 1.8.3 中首次发布。较旧的 PDFBox 版本需要对 PDFTextStripper 后代进行更多扩展更改。
  • 嗨 MKl,对于最近的沟通障碍,我很抱歉,我在城外。回到你的答案,我有一个问题。知道新版本(1.8.4)什么时候发布可用吗?有什么方法可以应用补丁来使用有效的 PDFTextStripper 功能。
  • 我不知道他们的发布时间表。但是 PDFBox 是开源的,该库的当前开发状态​​可以从 Apache 基金会的源代码控制中下载,您可以轻松构建一个已经包含修复的 jar。
【解决方案2】:

我对这个问题的解决方案是创建一个扩展 PDFTextStripper 类并覆盖函数的新类:

getCharactersByArticle()

注意:PDFBox 版本 1.8.5

CustomPDFTextStripper 类

public class CustomPDFTextStripper extends PDFTextStripper
{
    public CustomPDFTextStripper() throws IOException {
    super();
    }

    public Vector<List<TextPosition>> getCharactersByArticle(){
    return charactersByArticle;
    }
}

这样我可以解析 pdf 文档,然后从自定义提取函数中获取 TextPosition:

 private void extractTextPosition() throws FileNotFoundException, IOException {

    PDFParser parser = new PDFParser(new FileInputStream(pdf));
    parser.parse();
    StringWriter outString = new StringWriter();
    CustomPDFTextStripper stripper = new CustomPDFTextStripper();
    stripper.writeText(parser.getPDDocument(), outString);
    Vector<List<TextPosition>> vectorlistoftps = stripper.getCharactersByArticle();
    for (int i = 0; i < vectorlistoftps.size(); i++) {
        List<TextPosition> tplist = vectorlistoftps.get(i);
        for (int j = 0; j < tplist.size(); j++) {
            TextPosition text = tplist.get(j);
            System.out.println(" String "
          + "[x: " + text.getXDirAdj() + ", y: "
          + text.getY() + ", height:" + text.getHeightDir()
          + ", space: " + text.getWidthOfSpace() + ", width: "
          + text.getWidthDirAdj() + ", yScale: " + text.getYScale() + "]"
          + text.getCharacter());
        }       
    }
}

TextPositions 包含有关 pdf 文档字符的大量信息。

输出:

字符串 [x: 168.24, y: 64.15997, height:6.061287, space: 8.9664, width:3.4879303, yScale: 8.9664]J

字符串 [x: 171.69745, y: 64.15997, height:6.061287, space: 8.9664, width: 2.2416077, yScale:8.9664]N

字符串 [x: 176.25777, y: 64.15997, height:6.0343876, space: 8.9664,width: 6.4737396, yScale:8.9664]N

字符串 [x: 182.73778, y:64.15997, height:4.214208, space: 8.9664, width: 3.981079, yScale: 8.9664]e .....

【讨论】:

  • TextPositions 包含有关 pdf 文档字符的大量信息 - 但呈现模式和颜色是 OP 任务所需的信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-29
相关资源
最近更新 更多