如何使用 Java 和 itext 从 Graphics 对象创建包含多页的 PDF

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5290269/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 10:26:56  来源:igfitidea点击:

How to create a PDF with multiple pages from a Graphics object with Java and itext

javapdfitext

提问by Jan

I have an abstract class with an abstract method draw(Graphics2D g2), and the methods print(), showPreview(), printPDF(). For each document in my Java program, I implement draw(), so I can print, show preview and create a PDF file for each document. My problem is how to create a PDF with multiple pages from that Graphics object. I solved it by creating a PDF file for each page, and then merge the files into one new file. But there must be a better way. I have following code to create PDF with one page:

我有一个带有抽象方法 draw(Graphics2D g2) 和方法 print()、showPreview()、printPDF() 的抽象类。对于 Java 程序中的每个文档,我都实现了 draw(),因此我可以打印、显示预览并为每个文档创建一个 PDF 文件。我的问题是如何从该 Graphics 对象创建包含多个页面的 PDF。我通过为每一页创建一个 PDF 文件来解决它,然后将这些文件合并为一个新文件。但必须有更好的方法。我有以下代码可以用一页创建 PDF:

public void printPDF1(){
    JFileChooser dialog = new JFileChooser();
    String filePath = "";
    int dialogResult = dialog.showSaveDialog(null);
    if (dialogResult==JFileChooser.APPROVE_OPTION){
        filePath = dialog.getSelectedFile().getPath();
    }
    else return;
    try {
        Document document = new Document(new Rectangle(_pageWidth, _pageHeight));
        PdfWriter writer = PdfWriter.getInstance(document,
                new FileOutputStream(filePath));
        document.open();

        PdfContentByte cb = writer.getDirectContent();
        g2 = cb.createGraphics(_pageWidth, _height);
        g2.translate(0, (_numberOfPages - _pageNumber) * _pageHeight);
        draw(g2);
        g2.dispose();
        document.close();
    } 
    catch (Exception e2) {
        System.out.println(e2.getMessage());
    }
}

回答by Mark Storer

    document.open();

    // the same contentByte is returned, it's just flushed & reset during
    // new page events.
    PdfContentByte cb = writer.getDirectContent();

    for (int _pageNumber = 0; _pageNumber < _numberofPages; ++_numberOfPages) {
      /*******************/
      //harmless in first pass, *necessary* in others
      document.newPage(); 
      /*******************/

      g2 = cb.createGraphics(_pageWidth, _height);
      g2.translate(0, (_numberOfPages - _pageNumber) * _pageHeight);
      draw(g2);
      g2.dispose();
    }

    document.close();

So you're rendering your entire interface N times, and only showing a page-sized slice of it in different locations. That's called "striping" in print-world IIRC. Clever, but it could be more efficient in PDF.

因此,您将整个界面呈现 N 次,并且仅在不同位置显示页面大小的切片。这在印刷世界 IIRC 中称为“条纹”。聪明,但它在 PDF 中可能更有效。

Render your entire interface into one huge PdfTemplate (with g2d), once. Then draw that template into all your pages such that the portion you want is visible inside the current page's margins ("media box").

一次将您的整个界面渲染成一个巨大的 PdfTemplate(使用 g2d)。然后将该模板绘制到所有页面中,以便您想要的部分在当前页面的边距(“媒体框”)内可见。

PdfContentByte cb = writer.getDirectContent();
float entireHeight = _numberOfPages * _pageHeight;
PdfTemplate hugeTempl = cb.createTemplate( 0, -entireHeight, pageWidth, _pageHeight );
g2 = hugeTempl.createGraphics(0, -entireHeight, _pageWidth, _pageHeight ); 
draw(g2);
g2.dispose();

for (int curPg = 0; curPg < _numberOfPages; ++curPg) {
  cb.addTemplateSimple( hugeTempl, 0, -_pageHeight * curPg );

  document.newPage();
}

PDF's coordinate space sets 0,0 in the lower left corner, and those values increase as you go up and to the right. PdfGraphis2D does a fair amount of magic to hide that difference from you, but we still have to deal with it a bit here... thus the negative coordinates in the bounding box and drawing locations.

PDF 的坐标空间在左下角设置 0,0,这些值随着您向上和向右而增加。PdfGraphis2D 做了相当多的魔法来向你隐藏这种差异,但我们仍然需要在这里处理一下......因此边界框和绘图位置中的负坐标。

This is all "back of the napkin" coding, and it's entirely possible I've made a mistake or two in there... but that's the idea.

这都是“餐巾纸背面”编码,我完全有可能在那里犯了一个或两个错误……但这就是想法。

回答by Megatron

I was having trouble following the above code (some of the methods appear to have changed in the current itextpdf version). Here's my solution:

我在执行上面的代码时遇到了问题(在当前的 itextpdf 版本中,某些方法似乎发生了变化)。这是我的解决方案:

import com.itextpdf.awt.PdfGraphics2D;
import com.itextpdf.text.Document;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.WindowEvent;
import java.io.FileOutputStream;
import java.io.OutputStream;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;

public class PanelToPDF {

    private static JFrame frame= new JFrame();
    private static JPanel view= new JPanel();
    private static float pageWidth= PageSize.A4.getWidth();
    private static float pageHeight= PageSize.A4.getHeight();

    public static void main(String[] args) throws Exception {
        System.out.println("Page width = " + pageWidth + ", height = " + pageHeight);

        initPane();
        createMultipagePDF();

        frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
    }


    private static void initPane() {
        view.setLayout(new MigLayout());
        view.setBackground(Color.WHITE);

        for (int i= 1; i <= 160; ++i) {
            JLabel label= new JLabel("This is a test! " + i);
            label.setForeground(Color.BLACK);
            view.add(label, "wrap");

            JPanel subPanel= new JPanel();
            subPanel.setBackground(Color.RED);
            view.add(subPanel);
        }

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(Math.round(pageWidth), Math.round(pageHeight)));
        frame.add(view);
        frame.setVisible(true);
    }

    private static void createMultipagePDF() throws Exception {
        // Calculate the number of pages required. Use the preferred size to get
        // the entire panel height, rather than the panel height within the JFrame
        int numPages= (int) Math.ceil(view.getPreferredSize().height / pageHeight); // int divided by float

        // Output to PDF
        OutputStream os= new FileOutputStream("test.pdf");
        Document doc= new Document();
        PdfWriter writer= PdfWriter.getInstance(doc, os);
        doc.open();
        PdfContentByte cb= writer.getDirectContent();

        // Iterate over pages here
        for (int currentPage= 0; currentPage < numPages; ++currentPage) {
            doc.newPage(); // not needed for page 1, needed for >1

            PdfTemplate template= cb.createTemplate(pageWidth, pageHeight);
            Graphics2D g2d= new PdfGraphics2D(template, pageWidth, pageHeight * (currentPage + 1));
            view.printAll(g2d);
            g2d.dispose();

            cb.addTemplate(template, 0, 0);
        }

        doc.close();
    }