从 PDF 中提取不可选择的内容

Posted

技术标签:

【中文标题】从 PDF 中提取不可选择的内容【英文标题】:Extract unselectable content from PDF 【发布时间】:2016-01-07 23:49:19 【问题描述】:

我正在使用 Apache PDFBox 从 PDF 文件中提取页面,但我找不到提取无法选择的内容(文本或图像)的方法。可以从 PDF 文件中选择的内容没有问题。

请注意,有问题的 PDF 对复制内容没有任何限制,至少从我在文件的“文档限制摘要”中看到的内容来看:它们都允许“内容复制”和“可访问性的内容复制” ! 在同一个 PDF 文件中有可选择的内容和不可选择的其他部分。发生的情况是,提取的页面带有“漏洞”,即它们只有 PDF 的可选部分。但是,在 MS Word 上,如果我将 PDF 添加为对象,则会出现 PDF 页面的全部内容!所以我希望对 PDFBox lib 或任何其他 Java lib 做同样的事情!

这是我用来将 PDF 页面转换为图像的代码:

private void convertPdfToImage(File pdfFile, int pdfId) throws IOException 
   PDDocument document = PDDocument.loadNonSeq(pdfFile, null);
   List<PDPage> pdPages = document.getDocumentCatalog().getAllPages();
   for (PDPage pdPage : pdPages)  
       BufferedImage bim = pdPage.convertToImage(BufferedImage.TYPE_INT_RGB, 300);
       ImageIOUtil.writeImage(bim, TEMP_FILEPATH + pdfId + ".png", 300);
   
   document.close();

有没有办法使用这个 Apache PDFBox 库(或任何其他类似的库)?或者这根本不可能?如果确实不是,为什么?

非常感谢您的帮助!

编辑:我使用 Adob​​e Reader 作为 PDF 查看器和 PDFBox v1.8。这是一个示例 PDF:https://dl.dropboxusercontent.com/u/2815529/test.pdf

【问题讨论】:

您在 adobe 阅读过此 article 吗?我很确定您在处理某些受文档创建者版权保护的内容时遇到了问题。我也确信有一些方法可以绕过这种保护,但是 *** 不是应该讨论这些事情的地方。 不,这不是问题,有问题的 PDF 对复制内容没有任何限制,至少从我在文件的“文档限制摘要”中看到的内容来看 检查权限显然是我做的第一件事,只有世界上最愚蠢的人才会在没有先检查的情况下发布这样的问题。尽管如此,我还是编辑了我的问题,指出我已经进行了检查。 我问是因为你没有提到这个事实,也因为这里提出的问题中有一半可以在一分钟内用谷歌搜索或通过阅读一分钟的文档来解决。没什么私人的:) 我刚刚看了你的示例文件。实际上,在两种情况下,模式中存在图像,这使得它们在 Adob​​e Reader 中无法选择,并且无法由标准文本/图像提取解析器提取。这并不意味着它们不可提取,只是必须在这里进行一些编码。我今天不在办公室,但我明天某个时候会看看这个。 【参考方案1】:

有问题的两个图像,右上角的 fischer 徽标和稍微向下的小草图,每个都是通过用平铺图案填充页面上的一个区域来绘制的,该平铺图案依次在其内容流中绘制相应的图像。

Adobe Reader 不允许选择模式的内容,自动图像提取器通常也不遍历 Pattern 资源树。

PDFBox 1.8.10

您可以使用 PDFBox 轻松构建图案图像提取器,例如对于 PDFBox 1.8.10:

public void extractPatternImages(PDDocument document, String fileNameFormat) throws IOException

    List<PDPage> pages = document.getDocumentCatalog().getAllPages();
    if (pages == null)
        return;

    for (int i = 0; i < pages.size(); i++)
    
        String pageFormat = String.format(fileNameFormat, "-" + i + "%s", "%s");
        extractPatternImages(pages.get(i), pageFormat);
    


public void extractPatternImages(PDPage page, String pageFormat) throws IOException

    PDResources resources = page.getResources();
    if (resources == null)
        return;
    Map<String, PDPatternResources> patterns = resources.getPatterns();

    for (Map.Entry<String, PDPatternResources> patternEntry : patterns.entrySet())
    
        String patternFormat = String.format(pageFormat, "-" + patternEntry.getKey() + "%s", "%s");
        extractPatternImages(patternEntry.getValue(), patternFormat);
    


public void extractPatternImages(PDPatternResources pattern, String patternFormat) throws IOException

    COSDictionary resourcesDict = (COSDictionary) pattern.getCOSDictionary().getDictionaryObject(COSName.RESOURCES);
    if (resourcesDict == null)
        return;
    PDResources resources = new PDResources(resourcesDict);
    Map<String, PDXObject> xObjects = resources.getXObjects();
    if (xObjects == null)
        return;

    for (Map.Entry<String, PDXObject> entry : xObjects.entrySet())
    
        PDXObject xObject = entry.getValue();
        String xObjectFormat = String.format(patternFormat, "-" + entry.getKey() + "%s", "%s");
        if (xObject instanceof PDXObjectForm)
            extractPatternImages((PDXObjectForm)xObject, xObjectFormat);
        else if (xObject instanceof PDXObjectImage)
            extractPatternImages((PDXObjectImage)xObject, xObjectFormat);
    


public void extractPatternImages(PDXObjectForm form, String imageFormat) throws IOException

    PDResources resources = form.getResources();
    if (resources == null)
        return;
    Map<String, PDXObject> xObjects = resources.getXObjects();
    if (xObjects == null)
        return;

    for (Map.Entry<String, PDXObject> entry : xObjects.entrySet())
    
        PDXObject xObject = entry.getValue();
        String xObjectFormat = String.format(imageFormat, "-" + entry.getKey() + "%s", "%s");
        if (xObject instanceof PDXObjectForm)
            extractPatternImages((PDXObjectForm)xObject, xObjectFormat);
        else if (xObject instanceof PDXObjectImage)
            extractPatternImages((PDXObjectImage)xObject, xObjectFormat);
    

    Map<String, PDPatternResources> patterns = resources.getPatterns();

    for (Map.Entry<String, PDPatternResources> patternEntry : patterns.entrySet())
    
        String patternFormat = String.format(imageFormat, "-" + patternEntry.getKey() + "%s", "%s");
        extractPatternImages(patternEntry.getValue(), patternFormat);
    


public void extractPatternImages(PDXObjectImage image, String imageFormat) throws IOException

    image.write2OutputStream(new FileOutputStream(String.format(imageFormat, "", image.getSuffix())));

(ExtractPatternImages.java)

我像这样将它应用到您的示例 PDF 中

public void testtestDrJorge() throws IOException

    try (InputStream resource = getClass().getResourceAsStream("testDrJorge.pdf"))
    
        PDDocument document = PDDocument.load(resource);
        extractPatternImages(document, "testDrJorge%s.%s");;
    

(ExtractPatternImages.java)

得到两张图片:

`testDrJorge-0-R15-R14.png

testDrJorge-0-R38-R37.png

图像丢失了红色部分。这很可能是由于 PDFBox 版本 1.x.x 不正确支持 CMYK 图像的提取,参见。 PDFBOX-2128 (CMYK images are not supported correctly),您的图片是 CMYK 格式。

PDFBox 2.0.0 候选版本

我将代码更新为 PDFBox 2.0.0(目前仅作为候选版本提供):

public void extractPatternImages(PDDocument document, String fileNameFormat) throws IOException

    PDPageTree pages = document.getDocumentCatalog().getPages();
    if (pages == null)
        return;

    for (int i = 0; i < pages.getCount(); i++)
    
        String pageFormat = String.format(fileNameFormat, "-" + i + "%s", "%s");
        extractPatternImages(pages.get(i), pageFormat);
    


public void extractPatternImages(PDPage page, String pageFormat) throws IOException

    PDResources resources = page.getResources();
    if (resources == null)
        return;
    Iterable<COSName> patternNames = resources.getPatternNames();

    for (COSName patternName : patternNames)
    
        String patternFormat = String.format(pageFormat, "-" + patternName + "%s", "%s");
        extractPatternImages(resources.getPattern(patternName), patternFormat);
    


public void extractPatternImages(PDAbstractPattern pattern, String patternFormat) throws IOException

    COSDictionary resourcesDict = (COSDictionary) pattern.getCOSObject().getDictionaryObject(COSName.RESOURCES);
    if (resourcesDict == null)
        return;
    PDResources resources = new PDResources(resourcesDict);
    Iterable<COSName> xObjectNames = resources.getXObjectNames();
    if (xObjectNames == null)
        return;

    for (COSName xObjectName : xObjectNames)
    
        PDXObject xObject = resources.getXObject(xObjectName);
        String xObjectFormat = String.format(patternFormat, "-" + xObjectName + "%s", "%s");
        if (xObject instanceof PDFormXObject)
            extractPatternImages((PDFormXObject)xObject, xObjectFormat);
        else if (xObject instanceof PDImageXObject)
            extractPatternImages((PDImageXObject)xObject, xObjectFormat);
    


public void extractPatternImages(PDFormXObject form, String imageFormat) throws IOException

    PDResources resources = form.getResources();
    if (resources == null)
        return;
    Iterable<COSName> xObjectNames = resources.getXObjectNames();
    if (xObjectNames == null)
        return;

    for (COSName xObjectName : xObjectNames)
    
        PDXObject xObject = resources.getXObject(xObjectName);
        String xObjectFormat = String.format(imageFormat, "-" + xObjectName + "%s", "%s");
        if (xObject instanceof PDFormXObject)
            extractPatternImages((PDFormXObject)xObject, xObjectFormat);
        else if (xObject instanceof PDImageXObject)
            extractPatternImages((PDImageXObject)xObject, xObjectFormat);
    

    Iterable<COSName> patternNames = resources.getPatternNames();

    for (COSName patternName : patternNames)
    
        String patternFormat = String.format(imageFormat, "-" + patternName + "%s", "%s");
        extractPatternImages(resources.getPattern(patternName), patternFormat);
    


public void extractPatternImages(PDImageXObject image, String imageFormat) throws IOException

    String filename = String.format(imageFormat, "", image.getSuffix());
    ImageIOUtil.writeImage(image.getOpaqueImage(), "png", new FileOutputStream(filename));

得到

testDrJorge-0-COSNameR15-COSNameR14.png

testDrJorge-0-COSNameR38-COSNameR37.png

看起来像是一个改进... ;)

【讨论】:

为了完整起见:如果将整个文件转换为 RGB,图像是否正确提取? (非常出乎意料的是,我的 Acrobat Pro 丢弃了右上角的徽标,但确实将第二个图像转换为 RGB。) @Jongware Cf.我的编辑,使用 PDFBox 2.0.0 从模式中正确提取两个图像... ;)

以上是关于从 PDF 中提取不可选择的内容的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Android 中的印地语 PDF 文件中提取文本

怎样使用PDF编辑软件,怎么从PDF中提取单页

编辑PDF文件时如何提取页面

PDF如何提取页面?怎么提取PDF文件中的某一页

实用脚本!Python 提取 PDF 指定内容生成新文件!

实用脚本!Python 提取 PDF 指定内容生成新文件!