Java使用iText7生成PDF


  前言

  我們之前使用js庫html2canvas + jspdf實現html轉PDF、圖片,並下載(詳情請戳:html頁面轉PDF、圖片操作記錄),大致原理是將頁面塞到畫布里,以圖片的方式放到PDF中,生成的文件比較大,文本記錄Java使用iText7生成PDF

 

  iText 7是iText強大的PDF工具包的最新版本,用於PDF生成,PDF編程,處理和操作,如數字簽名等

  官方文檔:https://kb.itextpdf.com/home/it7kb/ebooks

 

  簡單生成PDF

  官方文檔:https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-jump-start-tutorial-for-java

  根據文檔說明,我們引入依賴

<properties>
    <!-- 省略其他部分...-->
    
    <itext7.version>7.1.7</itext7.version>
</properties>

<dependencies>
    <!-- 省略其他部分...-->
    
    <!-- itextpdf -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>kernel</artifactId>
        <version>${itext7.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>io</artifactId>
        <version>${itext7.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>layout</artifactId>
        <version>${itext7.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>forms</artifactId>
        <version>${itext7.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>pdfa</artifactId>
        <version>${itext7.version}</version>
    </dependency>
</dependencies>

 

 

  代碼

 

package cn.huanzi.qch.util;

import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.action.PdfAction;
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.Style;
import com.itextpdf.layout.element.*;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * itextpdf 工具類
 * iText 7是iText強大的PDF工具包的最新版本,用於PDF生成,PDF編程,處理和操作,如數字簽名等。
 * https://kb.itextpdf.com/home/it7kb/ebooks
 */
public class ITextPdfUtil {
    //字體,我這里使用系統自帶的simhei黑體
    private static final String FONT = "C:/Windows/Fonts/simhei.ttf";

    //html轉pdf
    public static void html2pdf(){
        String html = "";

        //從html文件讀取內容
        StringBuilder stringBuilder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader("E:\\Java\\html2pdf.html"));){
            for (Object o :  reader.lines().toArray()) {
                stringBuilder.append(o);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        html = stringBuilder.toString();

        try (PdfWriter writer = new PdfWriter("E:\\Java\\html2pdf.pdf");
             PdfDocument pdf = new PdfDocument(writer);
        ){
            //轉換器屬性設置
            ConverterProperties props = new ConverterProperties();
            //字體
            props.setFontProvider(new FontProvider());
            props.getFontProvider().addFont(ITextPdfUtil.FONT);
            //為img圖片配置基礎路徑
            props.setBaseUri("D:\\XFT User\\Pictures\\");

            //HtmlConverter.convertToDocument
            Document document = HtmlConverter.convertToDocument(html, pdf, props);

            //設置文檔屬性
            pdf.getDocumentInfo().setAuthor("huanzi-qch");
            pdf.getDocumentInfo().setTitle("IText測試html2pdf");
            pdf.getDocumentInfo().setSubject("XXX公司");
            pdf.getDocumentInfo().setMoreInfo("1","111");
            pdf.getDocumentInfo().setCreator("huanzi");
            pdf.getDocumentInfo().setKeywords("IText");

            //注冊事件監聽
            pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new MyEventHandler());

            //設置字體
            document.setFont(ITextPdfUtil.getPdfFont());

            //頁邊距
            document.setMargins(0, 0, 0, 0);

            document.close();
            System.out.println("操作完成!");
        }catch (IOException e){
            e.printStackTrace();
            System.err.println("操作異常...");
        }
    }

    //生成簡單PDF
    public static void test(){
        //語法糖
        try (PdfWriter writer = new PdfWriter("E:\\Java\\test.pdf");
             PdfDocument pdf = new PdfDocument(writer);
             Document document = new Document(pdf, PageSize.A4.rotate());
        ){

            //設置文檔屬性
            pdf.getDocumentInfo().setAuthor("huanzi-qch");
            pdf.getDocumentInfo().setTitle("IText測試PDF");
            pdf.getDocumentInfo().setSubject("XXX公司");
            pdf.getDocumentInfo().setMoreInfo("1","111");
            pdf.getDocumentInfo().setCreator("huanzi");
            pdf.getDocumentInfo().setKeywords("IText");

            //注冊事件監聽
            pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new MyEventHandler());

            //設置字體
            document.setFont(ITextPdfUtil.getPdfFont());

            //頁邊距
            document.setMargins(20, 20, 20, 20);

            //簡單文字
            document.add(new Paragraph("簡單文字"));
            document.add(new Paragraph("Hello Word!").add(new Tab()).add(new Text("你好!").addStyle(new Style().setFontSize(24))));

            //簡單圖片
            document.add(new Paragraph("簡單圖片"));
            document.add(new Image(ImageDataFactory.create("D:\\XFT User\\Pictures\\logo.png")));

            //簡單表格
            document.add(new Paragraph("簡單表格"));
            Table table = new Table(new float[]{3, 3, 4});
            PdfFont font = ITextPdfUtil.getPdfFont();
            //標題、內容
            process(table, "姓名;年齡;電話號碼", font, true);
            for (int i = 0; i < 5; i++) {
                process(table, "張三"+i+";"+(18+i)+";1500000000"+i, font, false);
            }
            document.add(table);

            //超鏈接
            document.add(new Paragraph("超鏈接"));
            PdfLinkAnnotation annotation = new PdfLinkAnnotation(new Rectangle(0, 0));
            annotation.setAction(PdfAction.createURI("https://itextpdf.com/"));
            Paragraph p = new Paragraph("更多精彩內容,猛戳:").add(new Link("這里", annotation));
            document.add(p);

            //換一頁
            //document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));

            document.close();
            System.out.println("操作完成!");

        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("操作異常...");
        }
    }

    //獲取統一字體
    public static PdfFont getPdfFont(){
        PdfFont pdfFont = null;
        try {
            pdfFont = PdfFontFactory.createFont(ITextPdfUtil.FONT, PdfEncodings.IDENTITY_H,true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return pdfFont;
    }

    //設置表格內容
    public static void process(Table table, String line, PdfFont font, boolean isHeader) {
        String[] split = line.split(";");
        for (String s : split) {
            Cell cell = new Cell().add(new Paragraph(s).setFont(font));
            if (isHeader) {
                table.addHeaderCell(cell);
            } else {
                table.addCell(cell);
            }
        }
    }

    /**
     * 自定義事件監聽
     *
     * 背景顏色
     * 頁腳頁眉
     * 文字水印
     *
     * 也可以分成多個EventHandler
     */
    protected static class MyEventHandler implements IEventHandler {

        @Override
        public void handleEvent(Event event) {
            PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
            PdfDocument pdfDoc = docEvent.getDocument();
            PdfPage page = docEvent.getPage();
            int pageNumber = pdfDoc.getPageNumber(page);
            Rectangle pageSize = page.getPageSize();
            PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDoc);

            //背景顏色
            Color backgroundColor = new DeviceRgb(245, 245, 245);;
            pdfCanvas.saveState()
                    .setFillColor(backgroundColor)
                    .rectangle(pageSize.getLeft(), pageSize.getBottom(),pageSize.getWidth(), pageSize.getHeight())
                    .fill().restoreState();

            //頁腳頁眉
            PdfFont pdfFont = ITextPdfUtil.getPdfFont();
            String header = "我是頁眉";
            String footer = "第 "+pageNumber+" 頁";
            pdfCanvas.beginText()
                    .setFontAndSize(pdfFont, 9)
                    .moveText((pageSize.getWidth() / 2) - (pdfFont.getWidth(header) / 200), pageSize.getTop() - 20)
                    .showText(header)
                    .moveText((pdfFont.getWidth(header) / 200) - (pdfFont.getWidth(footer) / 200), -pageSize.getTop() + 30)
                    .showText(footer)
                    .endText();

            //文字水印
            Canvas canvas = new Canvas(pdfCanvas, pdfDoc, page.getPageSize());
            canvas.setFontColor(new DeviceRgb(200, 200, 200));
            canvas.setProperty(20, 20);
            canvas.setFont(pdfFont);
            for (int i = 0; i < 5; i++) {
                for (int j = 0; j < 5; j++) {
                    canvas.showTextAligned(new Paragraph("我是文字水印").setOpacity(0.8f),(150 + i * 300), (160 + j * 150), pdfDoc.getPageNumber(page), TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45);
                }
            }

            pdfCanvas.release();
        }
    }

    //測試
    public static void main(String[] args) {
        test();

        //html2pdf();
    }
}

 

  效果

 

 

  生成的PDF

 

 

   文檔屬性

 

 

 

 

  HTML轉PDF

  官方文檔:https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-converting-html-to-pdf-with-pdfhtml

  pdfHTML是iText 7的一個附加組件,需要添加依賴

<properties>
    <!-- 省略其他部分...-->
    
    <itext7.html2pdf.version>2.1.4</itext7.html2pdf.version>
</properties>

<dependencies>
    <!-- 省略其他部分...-->
    
    <!-- itextpdf html2pdf組件-->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>html2pdf</artifactId>
        <version>${itext7.html2pdf.version}</version>
    </dependency>
</dependencies>

 

  代碼

  代碼同上,僅main測試函數不同!

    //測試
    public static void main(String[] args) {
        //test();

        html2pdf();
    }

  PS:html內容往下拉  

 

  效果

 

 

  html頁面

<!DOCTYPE html>
<html>
<head>
    <title>簡單簡歷</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate"/>
    <meta http-equiv="pragma" content="no-cache"/>
    <meta http-equiv="expires" content="0"/>

    <!-- 自定義樣式 -->
    <style>
        *{
            font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微軟雅黑",Arial,sans-serif;
        }

        body {
            margin: 0;
            padding: 0;
        }

        a {
            text-decoration: none;
            padding: 0;
            margin: 5px 0;
            color: black;
        }

        a:hover {
            color: #5c8dff;
        }

        b{
            margin: 0 10px;
        }

        /* 主體 */
        .main {
            /*margin: 0 auto;*/
            /*width: 770px;*/
            /*box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);*/
            /*border: 1px solid #dad8d8;*/
            /*background: #fbfbfb;*/
        }

        /* 1級標題 */
        h3.list1 {
            color: #409EFF;
            border-bottom: 1px solid #409EFF;
            padding: 5px;
            margin: 50px 0 0 0;
        }

        /* 大模塊 */
        .block,.block1 {
            /*background: #efefef;*/
            /*border: 1px solid #dad8d8;*/
            margin: 0 0 20px 0;
            padding: 0 10px;
        }
        .block,.block1 p {
            text-indent:2em;
        }

        .block:hover {
            background: #eeeeee;
        }
        .block1:hover {
            background: #cecece;
        }

        .button-list{
            text-align: center;
            margin: 20px auto;
            padding: 10px;
            width: 1024px;
        }
    </style>
</head>
<body>
<!-- 簡歷主體 -->
<div id="body" class="main">
    <div class="section">
        <div class="module">
            <h3 class="list1" style="margin: 0;">基本信息</h3>
            <div class="block">
                <p>XXX<b>/</b><b>/</b>25歲</p>
                <p>本科<b>/</b>XX學校<b>/</b>XX專業<b>/</b>2014-2018</p>
                <p>工齡:X年</p>
                <p>手機:XXXXXXXXXX</p>
                <p>郵箱:XXXXX@qq.com</p>
                <p>GitHub:<a href="https://github.com/huanzi-qch">https://github.com/huanzi-qch</a></p>
                <p>博客園:<a href="https://www.cnblogs.com/huanzi-qch">https://www.cnblogs.com/huanzi-qch</a></p>
                <br/>
                <p>求職崗位:Java開發<b>/</b>目標城市:南寧市<b>/</b>期望薪資:面議</p>
                <p>注:已離職,一個月可到崗</p>
                
                <img style="position: relative;top: -350px;left: 500px;width: 100px;height: 130px;" src="logo.png"/>
            </div>
        </div>

        <div class="module">
            <h3 class="list1">技能清單</h3>
            <div class="block">
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
                <p>熟悉XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。</p>
            </div>
        </div>

        <div class="module">
            <h3 class="list1">工作經歷</h3>
            <div class="block">
                <p>XXX技術有限公司<b>/</b>Java開發工程師<b>/</b>2018.01 - 至今</p>
                <p>參與多個項目開發、測試、部署等工作,包括:</p>
                <p>1、XXX。</p>
                <p>2、XXX。</p>
                <p>3、XXX。</p>
            </div>
        </div>

        <div class="module">
            <h3 class="list1">項目經歷</h3>
            <div class="block">
                <h4 class="list2">項目1</h4>
                <div class="block1">
                    <p>項目名稱:XXX(2020-11 —2021-11)</p>
                    <p>項目介紹:XXXXXXXXXXXXXXX。</p>
                    <p>技術架構:XXX + XXX + XXX。</p>
                    <p>職責描述:</p>
                    <p>1、XXXX。</p>
                    <p>2、XXXX。</p>
                    <p>3、XXXX。</p>
                    <p>4、XXXX。</p>
                </div>

                <h4 class="list2">項目2</h4>
                <div class="block1">
                    <p>項目名稱:XXX(2020-11 —2021-11)</p>
                    <p>項目介紹:XXXXXXXXXXXXXXX。</p>
                    <p>技術架構:SpringBoot + Vue + Element-UI + ECharts。</p>
                    <p>職責描述:</p>
                    <p>1、XXXX。</p>
                    <p>2、XXXX。</p>
                    <p>3、XXXX。</p>
                    <p>4、XXXX。</p>
                </div>
            </div>
        </div>

        <div class="module">
            <h3 class="list1">自我評價</h3>
            <div class="block">
                <p>1、XXXX;</p>
                <p>2、XXXX;</p>
                <p>3、XXXX;</p>
            </div>
        </div>
    </div>
</div>
</body>
</html>

 

 

  生成的PDF

 

  電子簽章

  2021-11-23更新

 

  提前准備

  1、keystore文件,生成自簽名證書,猛戳:SpringBoot系列——啟用https 

打開cmd,執行以下命令

keytool -genkeypair -alias stamper -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore e:/Java/stamper.keystore -storepass 123456

  2、印章圖片,這里有個在線制作電子公章小工具:http://makepic.net/tool/signet.html

 

 

 

 

  3、pom需要引入新依賴包

        <!-- 條形碼、電子簽章 -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>barcodes</artifactId>
            <version>${itext7.version}</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>hyph</artifactId>
            <version>${itext7.version}</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>font-asian</artifactId>
            <version>${itext7.version}</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>sign</artifactId>
            <version>${itext7.version}</version>
        </dependency>
        <!-- 加密軟件包 -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.69</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.69</version>
        </dependency>

  代碼

    /**
     * 電子簽章
     * @param src 需要簽章的pdf文件路徑
     * @param dest 簽完章的pdf文件路徑
     */
    public static void sign(String src, String dest)  {
        final String KEYSTORE = "E:\\Java\\stamper.keystore";//keystore文件路徑
        final char[] PASSWORD = "123456".toCharArray();// keystore密碼
        final String STAMPER_SRC = "E:\\Java\\stamper.gif";//印章圖片路徑

        try (PdfReader reader = new PdfReader(src); FileOutputStream os = new FileOutputStream(dest);){
            //讀取keystore ,獲得私鑰和證書鏈 jks
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream(KEYSTORE), PASSWORD);
            String alias = ks.aliases().nextElement();
            PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
            Certificate[] chain = ks.getCertificateChain(alias);

            //創建簽章工具PdfSigner、設定數字簽章的屬性
            PdfSigner stamper = new PdfSigner(reader, os, new StampingProperties());
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
            appearance.setReason("簽名原因:系統自動簽名蓋章");
            appearance.setLocation("簽名地點:xxx系統");
            appearance.setContact("聯系方式:huanzi.qch@qq.com");

            //加蓋圖章圖片
            ImageData img = ImageDataFactory.create(STAMPER_SRC);
            Image image = new Image(img);
            appearance.setPageNumber(1);
            appearance.setPageRect(new Rectangle(650, 50, image.getImageWidth(), image.getImageHeight()));
            appearance.setSignatureGraphic(img);
            appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

            //No such provider: BC : 問題解決,加BC庫支持
            Security.addProvider(new BouncyCastleProvider());

            //摘要算法
            IExternalDigest digest = new BouncyCastleDigest();
            //簽名算法
            IExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, BouncyCastleProvider.PROVIDER_NAME);

            //調用itext簽名方法完成pdf簽章
            stamper.setCertificationLevel(1);
            stamper.signDetached(digest,signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);

            System.out.println("操作完成!");
        }catch (Exception e){
            e.printStackTrace();
            System.err.println("操作異常...");
        }
    }

 

  效果

  我們用 test() 生成的簡單PDF文件來進行電子簽章測試

    //測試
    public static void main(String[] args) {
        //test();

        //html2pdf();

        sign("E:\\Java\\test.pdf","E:\\Java\\test2.pdf");
    }

 

 

 

 

 

  后記

  不管是前端生成PDF,還是后端生成PDF,能實現需求就是好技術!   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM