如何使用Apache PDFBox从PDF中的按钮图标中提取图像?

3
我想使用Java NetBeans从PDF中的按钮获取图像图标,并将其放置在某些面板中。然而,我遇到了麻烦。 我正在使用PDFBox作为我的PDF导出器,但似乎无法理解足够多的信息。 我已经成功地从表单字段中读取数据,但是在试图在PDFBox中找到按钮提取器时却没有找到。 我应该如何做?是否可以使用这种方法,或者有其他方法可以解决问题? 谢谢提前帮助。
编辑: 我已经找到了用以下代码提取图像的示例实用程序:
       File myFile = new File(filename);
        try { 

            //PDDocument pdDoc = PDDocument.loadNonSeq( myFile, null );
            PDDocument pdDoc = null;
            pdDoc = PDDocument.load( myFile );
            PDDocumentCatalog pdCatalog = pdDoc.getDocumentCatalog();
            PDAcroForm pdAcroForm = pdCatalog.getAcroForm();
            // dipakai untuk membaca isi file

            List pages = pdDoc.getDocumentCatalog().getAllPages();
            Iterator iter = pages.iterator();
             while( iter.hasNext() )
             {
                 PDPage page = (PDPage)iter.next();
                 PDResources resources = page.getResources();
                 Map images = resources.getImages();
                 if( images != null )
                 {
                     Iterator imageIter = images.keySet().iterator();
                     while( imageIter.hasNext() )
                     {
                         String key = (String  )imageIter.next();
                         PDXObjectImage image = (PDXObjectImage)images.get(key);
                         BufferedImage imagedisplay= image.getRGBImage();
                         jLabel5.setIcon(new ImageIcon(imagedisplay)); // NOI18N                                 
                     }
                 }
             }


        } catch (Exception e) {
               JOptionPane.showMessageDialog(null, "error " + e.getMessage());


        }

然而,我仍然无法从按钮图像中读取。顺便说一下,我阅读了这个页面上的教程,以将按钮图像添加到PDF中。 https://acrobatusers.com/tutorials/how-to-create-a-button-form-field-to-insert-a-pdf-file
第二次编辑: 在这里,我还给您链接到具有图标的PDF。PDF Link。 预先感谢您。

1
这只是一次性的事情吗?请把PDF上传到某个地方。 - Tilman Hausherr
不,我正在将其用于应用程序,因此我会多次使用它。我已经阅读了将所有页面制作成图像,然后提取它的方法,但是我仍在尝试实现它。谢谢您的快速回答。 - Sudarmadji Suryono
PDFBox源代码中有一个ExtractImages示例实用程序,您可以尝试使用它。如果不成功,您应该使用PDFDebugger命令实用程序来查找“按钮”所在的位置(可能在Acroform部分)。请使用PDFDebugger的2.0版本。https://repository.apache.org/content/groups/snapshots/org/apache/pdfbox/pdfbox-debugger/ - Tilman Hausherr
谢谢。我会尝试阅读它,如果有答案的话,我会在这里发布。 - Sudarmadji Suryono
你的代码查看页面内容图像资源;但是,按钮不是内容的一部分,而是有自己的资源。你能分享一个包含这样一个图像按钮的示例文件以供分析吗? - mkl
1个回答

5
我假设您所说的“PDF中的按钮”是指交互式表单按钮。

总体而言

在PDFBox中,没有专门用于提取按钮的图标的工具。但是,由于按钮(以及注释通常)使用自定义图标作为外观的一部分来定义这些图标,因此可以简单地(递归地)遍历注释的外观资源并收集子类型为ImageXObject

public void extractAnnotationImages(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");
        extractAnnotationImages(pages.get(i), pageFormat);
    }
}

public void extractAnnotationImages(PDPage page, String pageFormat) throws IOException
{
    List<PDAnnotation> annotations = page.getAnnotations();
    if (annotations == null)
        return;

    for (int i = 0; i < annotations.size(); i++)
    {
        PDAnnotation annotation = annotations.get(i);
        String annotationFormat = annotation.getAnnotationName() != null && annotation.getAnnotationName().length() > 0
                ? String.format(pageFormat, "-" + annotation.getAnnotationName() + "%s", "%s")
                : String.format(pageFormat, "-" + i + "%s", "%s");
        extractAnnotationImages(annotation, annotationFormat);
    }
}

public void extractAnnotationImages(PDAnnotation annotation, String annotationFormat) throws IOException
{
    PDAppearanceDictionary appearance = annotation.getAppearance();
    extractAnnotationImages(appearance.getDownAppearance(), String.format(annotationFormat, "-Down%s", "%s"));
    extractAnnotationImages(appearance.getNormalAppearance(), String.format(annotationFormat, "-Normal%s", "%s"));
    extractAnnotationImages(appearance.getRolloverAppearance(), String.format(annotationFormat, "-Rollover%s", "%s"));
}

public void extractAnnotationImages(Map<String, PDAppearanceStream> stateAppearances, String stateFormat) throws IOException
{
    if (stateAppearances == null)
        return;

    for (Map.Entry<String, PDAppearanceStream> entry: stateAppearances.entrySet())
    {
        String appearanceFormat = String.format(stateFormat, "-" + entry.getKey() + "%s", "%s");
        extractAnnotationImages(entry.getValue(), appearanceFormat);
    }
}

public void extractAnnotationImages(PDAppearanceStream appearance, String appearanceFormat) throws IOException
{
    PDResources resources = appearance.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(appearanceFormat, "-" + entry.getKey() + "%s", "%s");
        if (xObject instanceof PDXObjectForm)
            extractAnnotationImages((PDXObjectForm)xObject, xObjectFormat);
        else if (xObject instanceof PDXObjectImage)
            extractAnnotationImages((PDXObjectImage)xObject, xObjectFormat);
    }
}

public void extractAnnotationImages(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)
            extractAnnotationImages((PDXObjectForm)xObject, xObjectFormat);
        else if (xObject instanceof PDXObjectImage)
            extractAnnotationImages((PDXObjectImage)xObject, xObjectFormat);
    }
}

public void extractAnnotationImages(PDXObjectImage image, String imageFormat) throws IOException
{
    image.write2OutputStream(new FileOutputStream(String.format(imageFormat, "", image.getSuffix())));
}

(来自 ExtractAnnotationImageTest.java)

很遗憾,OP没有提供样例PDF文件,所以我将代码应用到了这个示例文件上。

buttons.pdf screenshot

(作为资源存储)如下:

/**
 * Test using <a href="http://examples.itextpdf.com/results/part2/chapter08/buttons.pdf">buttons.pdf</a>
 * created by <a href="http://itextpdf.com/examples/iia.php?id=154">part2.chapter08.Buttons</a>
 * from ITEXT IN ACTION — SECOND EDITION.
 */
@Test
public void testButtonsPdf() throws IOException
{
    try (InputStream resource = getClass().getResourceAsStream("buttons.pdf"))
    {
        PDDocument document = PDDocument.load(resource);
        extractAnnotationImages(document, new File(RESULT_FOLDER, "buttons%s.%s").toString());;
    }
}

(来自 ExtractAnnotationImageTest.java)

并获得了这些图片:

buttons-0-10-Normal-default-FRM-img0.png

并且

buttons-0-10-Normal-default-FRM-img1.png

这里有两个问题:

  • 我们提取了注释外观附加的所有图像资源,但没有检查它们是否实际上在外观流中被使用。因此,你可能会发现比预期更多的图标。在上面的例子中,第一个图像不是作为单独的资源使用,而只是作为第二个图像的蒙版。
  • 我们仅提取图像资源,不包括行内图像,因此可能会错过一些图像。

因此,请使用你的PDF文件检查此代码。如有必要,可以进行改进。

原帖中的文件

与此同时,OP提供了一个示例文件 imageicon.pdf

imageicon.pdf screenshot

像这样调用上面的方法
/**
 * Test using <a href="http://www.docdroid.net/TDGVQzg/imageicon.pdf.html">imageicon.pdf</a>
 * created by the OP.
 */
@Test
public void testImageiconPdf() throws IOException
{
    try (InputStream resource = getClass().getResourceAsStream("imageicon.pdf"))
    {
        PDDocument document = PDDocument.load(resource);
        extractAnnotationImages(document, new File(RESULT_FOLDER, "imageicon%s.%s").toString());;
    }
}

(来自ExtractAnnotationImageTest.java)

输出以下图片:

imageicon-0-0-Normal-default-FRM-NxIm0.jpg

因此,它工作得非常好!

作为独立工具启动

OP在评论中表示仍然对使用junit测试方法感到困惑,但是当我尝试将其调用到我的主程序中时,它总是返回“流关闭”错误。我已经将文件放在与我的jar相同的目录中,并尝试手动提供路径,但仍然出现相同的错误。

因此,我添加了一个main方法到该类中,使其可以:

  1. 在没有JUnit框架的情况下启动;
  2. 从命令行中给定的本地文件系统中的任何PDF文件中提取信息。

代码如下:

public static void main(String[] args) throws IOException
{
    ExtractAnnotationImageTest extractor = new ExtractAnnotationImageTest();

    for (String arg : args)
    {
        try (PDDocument document = PDDocument.load(arg))
        {
            extractor.extractAnnotationImages(document, arg+"%s.%s");;
        }
    }
}

(来自ExtractAnnotationImageTest.java)


非常感谢您详细而快速的回答。我已经尝试了您的代码,但是它返回了一个错误。它说源代码无法编译 - 错误的树类型:(任何)。我真的不明白发生了什么。我还在此链接中上传了我尝试提取的PDF文件link。提前致谢。 - Sudarmadji Suryono
无法编译的源代码 - 错误的树类型:(任何)- 这听起来根本不像Java编译错误消息...您使用了Java文件的GitHub副本还是从答案中复制并粘贴了代码? - mkl
给 @mkl :我已经从github上复制了代码,但是我仍然不清楚如何使用junit测试方法,当我尝试将其调用到我的主程序中时,它总是返回“流关闭”错误。我已经将文件放在与我的jar相同的目录中,并尝试手动提供路径,但仍然出现相同的错误。我知道你已经帮助了我很多,这是因为我缺乏编程技能,但是你能给我一个代码片段,让我可以在我的主程序中调用提取方法而没有任何错误吗?提前致谢。 - Sudarmadji Suryono
我添加了一个main方法,使得该类可以作为独立工具使用,通过命令行给定PDF文件名进行处理。 - mkl
谢谢,你是一个非常棒的人。无论如何,有时候当图片还没有上传时,它会返回空指针异常的错误,而我添加了一行代码来帮助预防这个错误,那就是在第149行加入:if (appearance == null) return;。我希望这对任何读到这个问题的人都有所帮助。非常感谢@mkl,给你两个大拇指。 - Sudarmadji Suryono

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接