1

我已经设法使用来自 Apache 的 Javafx 和 PDFbox 将文本放入 pdf 文件中。现在我正在尝试将图像从拍摄场景的屏幕截图放置到 pdf 文档中。

    WritableImage snapshot = quotes.getScene().snapshot(null);
    PDDocument doc = null;
    PDPage page = null;
    PDXObjectImage ximg = null;
    BufferedImage bufferedImg = SwingFXUtils.fromFXImage(snapshot, null);

   try{
       doc = new PDDocument();
       page = new PDPage();


       doc.addPage(page);
       PDPageContentStream content = new PDPageContentStream(doc, page);


      /* ByteArrayOutputStream out = new ByteArrayOutputStream();
      InputStream in = new ByteArrayInputStream(out.toByteArray());*/

      ximg = new PDJpeg(doc, bufferedImg);
      //ximg = new PDJpeg(doc, in);
      content.drawImage(ximg, 100, 100);

      content.close();
      doc.save("PDFWithText.pdf");
      doc.close();
    } catch (Exception e){
    System.out.println(e);
    }

当我使用 InputStream 和 OutputStream 类时,会创建文档,但是当我打开它时,Adobe 和其他程序给我一个错误,说 Acrobat 无法正确显示页面。当我使用上面代码中注释掉的 BufferedImage 类时,文档很简单,没有创建,我不知道为什么。

笔记

这是对 JavaFx 2.2 中的场景或场景的一部分进行截图的后续问题,但特定于 pdf 创建部分而不是节点快照部分。

4

2 回答 2

4

这不是一个实际的解决方案(因为提供的代码不起作用),而是我尝试解决问题的文档

我花了一些时间试图解决这个问题,但无法解决。我已经包含了我的解决方案的示例代码,因为它提供了一个 SSCCE 和方法,可以帮助其他人尝试解决这个问题。

我尝试的两种方法是:

  1. 通过 JavaFX 方法 SwingFXUtils.fromFXImage 获取 BufferedImage 输出并将其用作 pdfbox api 的输入。

    这种方法不起作用,因为 SwingFXUtils 创建的 BufferedImage 使用与 pdfbox api 所需的不兼容的 SampleModel 进行编码。

  2. 通过 JavaFX 方法 SwingFXUtils.fromFXImage 从 BufferedImage 输出中创建 Jpeg 流(使用 ImageIO),并将其用作 pdfbox api 的输入。

    这种方法不起作用。失败的原因可能是 ImageIO 从 SwingFXUtils 创建的 BufferedImage 创建了粉红色(即编码错误)的 jpeg。这可能是 ImageIO 中的一个错误。此外,我如何使用 pdfbox 将结果图像添加到 jpeg 中可能存在错误。

推荐

还有许多其他用于从 JavaFX 创建 PDF 文件的 api。与其继续对 JavaFX 输出的图像与 pdfbox 的集成进行故障排除,我建议尝试任何其他 api 来执行创建 pdf 文件的任务(谷歌搜索会显示它们)。

可执行示例代码

import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.*;
import java.io.*;
import java.nio.file.Paths;
import java.util.Arrays;

// #### THIS CODE CURRENTLY DOES NOT FUNCTION CORRECTLY - SEE INLINE COMMENTS IN THE CODE TO UNDERSTAND WHY ####
//
// Demonstrates converting a JavaFX SceneGraph to a pdf (just as a bitmapped image, not as vector graphics).
//   1. creating a snapshot of a JavaFX node.
//   2. creating a pdf from the snapshot (using apache pdfbox http://pdfbox.apache.org/).
//   3. saving the pdf to a file.
//   4. opening the saved pdf in a web browser so the web browser can trigger showing
//      the pdf in an appropriate pdf viewer (if the user has such a viewer installed).
public class PdfWithImageCreator extends Application {
    // icon courtesy of http://www.aha-soft.com/ not for commercial use (free for non-commercial use).
    private static final String imageURL =
            "http://icons.iconarchive.com/icons/aha-soft/free-global-security/512/Global-Network-icon.png";

    private static final String PDF_PATH =
            Paths.get("exported.pdf").toAbsolutePath().toString();

    @Override public void start(Stage stage) {
        VBox layout = new VBox(20);

        ImageView imageView = new ImageView(
            new Image(imageURL)
        );

        Button export = new Button("Export");
        export.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                exportToPDF(layout, PDF_PATH);
            }
        });

        layout.setStyle("-fx-font-size: 20px;");
        layout.setAlignment(Pos.CENTER);
        layout.setPadding(new Insets(20));
        layout.getChildren().setAll(
            export,
            imageView
        );

        stage.setScene(
            new Scene(
                new Group(
                    layout
                )
            )
        );

        stage.show();
    }

    /**
     * Snapshots the provided node.
     * Encodes the snapshot in a pdf.
     * Saves the pdf to the provided file path.
     * Opens the pdf in the default system web browser.
     *
     * @param node the node for which the snapshot is to be taken.
     * @param filePath the path where the pdf is to be saved.
     */
    private void exportToPDF(Node node, String filePath){
        PDDocument doc = null;
        PDPage page = null;
        PDPageContentStream content = null;
        PDXObjectImage ximage = null;

        try {
            // snapshot the node and convert it to an awt buffered image.
            Image fxImage = node.snapshot(null, null);
            BufferedImage bufferedImage = SwingFXUtils.fromFXImage(fxImage, null);

            // create a pdf containing the snapshot image.
            doc = new PDDocument();
            page = new PDPage();
            doc.addPage(page);

            content = new PDPageContentStream(doc, page);

            // alternate path A => try to create a PDJpeg from a jpegInputStream.
            ximage = createPDJpegFromJpegStream(doc, bufferedImage);

            // alternate path B => try to create a PDJpeg from directly from a BufferedImage directly.
            // ximage = createPDJpegFromBufferedImage(doc, bufferedImage);

            content.drawImage(ximage, 0, 0);
            content.close();

            // save the created image to disk.
            doc.save(filePath);

            System.out.println("Exported PDF to: " + filePath);

            // show the generated pdf in a web browser.
            // (if the browser is pdf enabled, this will display the pdf in the web browser).
            getHostServices().showDocument(filePath);
        } catch(IOException | COSVisitorException ie) {
            ie.printStackTrace();
        } finally {
            try {
                if (content != null) { content.close(); }
                if (doc     != null) { doc.close(); }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // #### THIS METHOD DOES NOT FUNCTION AS EXPECTED
    // alternate path => try to create a PDJpeg from a jpegInputStream.
    // when using a jpeg stream this doesn't work, the created pdf is not well formed and
    // you end up with adobe pdf reader running out of memory trying to read the resultant pdf.
    // Also outputs a weird message that I currently don't understand =>
    //    INFO: About to return NULL from unhandled branch. filter = COSName{DCTDecode}
    private PDXObjectImage createPDJpegFromJpegStream(PDDocument doc, BufferedImage bufferedImage) throws IOException {
        // provide the buffered image data as input to a jpeg input stream.
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        ImageOutputStream jpegImageOutputStream = ImageIO.createImageOutputStream(jpegOutputStream);
        ImageIO.write(bufferedImage, "jpeg", jpegImageOutputStream);
        InputStream jpegInputStream = new ByteArrayInputStream(
                Arrays.copyOf(jpegOutputStream.toByteArray(), jpegOutputStream.size())
        );

        // output created jpg file for debugging purposes
        // => when you view it is pink due to (I believe) an ImageIO bug.
        // you can see how the resultant image is pink by opening the image file named in system.out in any image viewer.
        // this improper encoding of the jpeg data may be why the subsequent use of it to generate a pdf
        // will generate a an invalid pdf.
        File file = new File("output.jpg");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(Arrays.copyOf(jpegOutputStream.toByteArray(), jpegOutputStream.size()));
        System.out.println(file.getAbsolutePath());

        return new PDJpeg(doc, jpegInputStream);
    }

    // #### THIS METHOD DOES NOT FUNCTION AS EXPECTED
    // alternate path => try to create a PDJpeg from directly from a BufferedImage directly, get the following exception:
    // Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Raster IntegerInterleavedRaster: width = 552 height = 616 #Bands = 1 xOff = 0 yOff = 0 dataOffset[0] 0 is incompatible with ColorModel ColorModel: #pixelBits = 8 numComponents = 1 color space = java.awt.color.ICC_ColorSpace@125fe1b6 transparency = 1 has alpha = false isAlphaPre = false
    //    at java.awt.image.BufferedImage.<init>(BufferedImage.java:630)
    // Browsing the awt PDJpeg and awt code it appears that the BufferedImage returned by JavaFX uses a
    // SinglePixelPackedSampleModel, but PDJpeg required the buffered image to use a ComponentColorModel
    // and the two are incompatible.  So the bufferedimage needs to be re-encoded to a compatible
    // raster format that utilizes a SampleModel (i.e. a ComponentColorModel) that is acceptable by PDJpeg.
    //
    private PDXObjectImage createPDJpegFromBufferedImage(PDDocument doc, BufferedImage bufferedImage) throws IOException {
        return new PDJpeg(doc, bufferedImage);
    }

    public static void main(String[] args) { launch(args); }
}
于 2013-08-06T00:11:06.900 回答
1

我通过将图像转换为PNG并首先将其写入Outputstream并将转换后的PNG从输出流读取到BufferedImage来将快照转换为PDF。

最终的 BufferedImage 在 PDJpeg 中渲染良好,没有任何问题。

这是我的代码。

public static BufferedImage generate_png_from_container(Node node) {
        SnapshotParameters param = new SnapshotParameters();
        param.setDepthBuffer(true);
        WritableImage snapshot = node.snapshot(param, null);
        BufferedImage tempImg = SwingFXUtils.fromFXImage(snapshot, null);
        BufferedImage img = null;
        byte[] imageInByte;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(tempImg, "png", baos);
            baos.flush();
            imageInByte = baos.toByteArray();
            baos.close();
            InputStream in = new ByteArrayInputStream(imageInByte);
            img = ImageIO.read(in);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       //the final image sent to the PDJpeg
       return img;
}
于 2013-11-06T12:09:46.043 回答