java生成pdf並加水印,通過wkhtmltopdf實現


公司項目有個需求就是根據模板動態的生成pdf文件,在網上看過很多方式生成的效果都不是很好。要么樣式不支持,要么字體不支持。我這邊項目的需求是這樣:

1.根據模板生成紙張方向為橫向的pdf
2.給pdf的每一頁中間的位置都加上文字水印

研究了很久終於實現了效果,效果如下圖所示:

image-20210801204853727

總體實現方案

  • 准備好html模板 --->就是寫html,注意:樣式少用css3的,並且不要使用外部樣式.可以把樣式寫在style標簽內部,也就是保證樣式和html標簽在同一個html文件中。

  • 通過模板引擎將html模板生成含數據的html ---> 使用beetl模板引擎實現,當然你也可以用freemarker等模板引擎實現

  • 把生成的html轉成pdf--->通過wkhtmltopdf這個程序實現

  • 最后把生成的pdf文件加上水印--->通過itext實現

1 編寫模板

模板如下,其實就是寫html文件,可以看到${xxx}這種占位符,這些數據都是動態的,用來給模板引擎識別的。

image-20210801205236360

2 模板引擎生成帶數據的html文件

因為公司項目用的是beetl模板引擎所以我這里也就用beetl生成html文件.你也可以用其它的模板引擎生成,比如freemarker。如果這兩個都不會,那就先去學習一下吧,O(∩_∩)O哈哈~,其實不難,這兩個模板引擎的語法和jsp的語法很像。

beetl官網:https://www.kancloud.cn/xiandafu/beetl3_guide/2138944

freemarker官網:http://freemarker.foofun.cn/

beetl的maven依賴如下:

<dependency>
    <groupId>com.ibeetl</groupId>
    <artifactId>beetl</artifactId>
    <version>3.1.8.RELEASE</version>
</dependency>

大致代碼如下:這里需要根據你自己的模板進行修改

public static void fileResourceLoaderTest() throws Exception{
		String root = System.getProperty("user.dir")+File.separator+"template";
		//System.out.println(root);
		FileResourceLoader resourceLoader = new FileResourceLoader(root,"utf-8");
		Configuration cfg = Configuration.defaultConfiguration();
		GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
		//准備模板需要的動態數據
		Map map = new HashMap();//給模板的數據
		map.put("address", "清遠市清城區橫荷清遠大道55號");
		//通過模板位置構建模板對象
		Template t = gt.getTemplate("/s01/order.html");
		//將數據和模板綁定,
		t.binding(map);
		String str = t.render();
		System.out.println(str);
		//生成html文件
		FileWriter writer = new FileWriter("C:\\Users\\Administrator\\Desktop\\a\\1.html");
		writer.write(str);
	}

3 通過wkhtmltopdf將生成的html文件轉成pdf文件

wkhtmltopdf是一個程序,具體來說就是一個軟件,也是生成pdf的關鍵技術。可以在java程序中調用這個軟件來把html文件轉成pdf文件。我對比了很多方案,我認為wkhtmltopdf是目前來說比較好的一種方案。

3.1 下載wkhtmltopdf

官網下載地址:https://wkhtmltopdf.org/downloads.html

根據自己的需要下載相應的版本即可

image-20210802230511892

3.2 安裝wkhtmltopdf

下載下來的安裝包如下:

image-20210802230435552

3.2.1 windows安裝

windows安裝比較簡單,一直點擊下一步即可

裝好之后可以使用cmd命令行來運行

在裝好的路徑下右鍵--->打開命令窗口:

image-20210802232012602

命令格式:

wkhtmltopdf.exe https://wkhtmltopdf.org/downloads.html c:\1.pdf

image-20210802232308095

可以看到其實就是把https://wkhtmltopdf.org/downloads.html這個網頁在c盤下生成1.pdf

image-20210802232335699

當然也可以把本地的html文件轉成pdf,比如:

wkhtmltopdf.exe c:\1.html c:\1.pdf

這樣生成的pdf默認是豎向的,但是公司項目需求是橫向的所以要加上參數 -O landscape

wkhtmltopdf -O landscape c:\1.html c:\1.pdf

其它參數配置可以看看這個博客:wkhtmltopdf 參數 詳解

3.2.2 centos7-linux安裝

  • 首先需要安裝以下依賴:
yum install libX11
yum install libXext
yum install libXrender
yum install libjpeg
yum install xorg-x11-fonts-75dpi
yum install xorg-x11-fonts-Type1
  • 然后執行安裝命令
rpm -ivh wkhtmltox-0.12.6-1.centos7.x86_64.rpm

至此linux上就安裝好了,linux的運行wkhtmltopdf和windows類似

比如將當前目錄下的1.html轉成1.pdf

/opt/wkhtmltopdf/bin/wkhtmltopdf -O landscape .\1.html .\1.pdf

/opt/wkhtmltopdf/bin/wkhtmltopdf其實就是裝好之后的路徑,可以通過find命令查出來.每台linux的服務器可能不一樣,所以要查一下。

find / -name 'wkhtmltopdf'

比如我查出來的結果如下:

image-20210802234806791

3.3 封裝一個html轉pdf的工具類

public class WKHtmlToPdfUtil {
	
	/**
	 *       測試
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception{
		htmlToPdf("https://www.baidu.com","d:\\1.pdf");
    }

	/**
	 * 獲取windows和linux調用wkhtmltopdf程序的命令
	 * @param sourceFilePath
	 * @param targetFilePath
	 * @return
	 */
	public static String getCommand(String sourceFilePath, String targetFilePath) {
        String system = System.getProperty("os.name");
        if(system.contains("Windows")) {
        	System.out.println("windows");
        	//-O landscape 表示紙張方向為橫向  默認為縱向  如果要生成橫向那么去掉-O landscape即可
            return "C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe -O landscape " + sourceFilePath + " " + targetFilePath;
        }else if(system.contains("Linux")) {
        	System.out.println("linux");
            return "/usr/local/bin/wkhtmltopdf -O landscape " + sourceFilePath + " " + targetFilePath;
        }
        return "";
    }
	
	/**
	 * html轉pdf
	 * @param sourceFilePath
	 * @param targetFilePath
	 * @return
	 */
	public static boolean htmlToPdf(String sourceFilePath, String targetFilePath) {
		try {
			System.out.println("轉化開始");
			String command = WKHtmlToPdfUtil.getCommand(sourceFilePath, targetFilePath);
			System.out.println("command:"+command);
	        Process process = Runtime.getRuntime().exec(command);
	        process.waitFor();  //這個調用比較關鍵,就是等當前命令執行完成后再往下執行
	        System.out.println("html-->pdf:success");
	        return true;
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("html-->pdf:error");
			return false;
		}
	}
}

3.4 wkhtmltopdf的一些問題總結

3.4.1 強制分頁問題

添加樣式,使用樣式的容器將會獨占一頁,如果分頁最后一頁也會獨占一頁,給div容器加上以下樣式即可。

page-break-after: always !important;

3.4.2 每頁顯示表頭

thead {
     display:table-header-group;/* 給thead加上這行保證每頁都會帶上表頭 */
}

給thead加上這行保證每頁都會帶上表頭,同時也會解決表頭與內容重疊問題.

如果仍然存在表頭和內容重疊問題,一般是因為表格的一行的內容超過一頁。我就遇到過這種問題,我的解決方案是用js縮放頁面。

document.getElementsByTagName('body')[0].style.zoom=0.6;//0.6為縮放的比例

3.4.3 表格分頁時行內容被截斷問題

給表格tbody的tr標簽加上這個樣式

tbody tr{
	page-break-inside: avoid !important;
}

4 給pdf的每一頁都加上文字水印和頁碼

如果你的項目沒有這個需求可以不看這部分

maven坐標如下:

<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itextasian</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itext</artifactId>
    <version>2.1.7</version>
</dependency>

其中itextasian這個maven倉庫默認是沒有的,需要手動添加到自己的maven倉庫

我上傳到百度雲了:

鏈接:https://pan.baidu.com/s/1bN3MTRjzlQaqzF5FpfpySg
提取碼:ijgf

下載下來后將lowagie.rar 中的 lowagie 文件夾 直接拷貝到本地倉庫的com文件夾下面即可

image-20210803232811879

demo代碼如下

import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.junit.Test;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Image;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfGState;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;

public class PDFAddWaterMart{
	
    @Test
    public void addTextMart() throws Exception{
    	// 要輸出的pdf文件
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("C:\\result1.pdf")));
        // 將pdf文件先加水印然后輸出
        setWatermarkText(bos, "C:\\input.pdf","壹新設計報價雲平台");
    }
    
    //@Test
    public void addImageMart() throws Exception{
    	// 要輸出的pdf文件
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("C:\\result2.pdf")));
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        // 將pdf文件先加水印然后輸出
        setWatermarkImage(bos, "C:\\input.pdf", format.format(cal.getTime()) + "  下載使用人:" + "測試user", 16);
    }
    
    
    /**
     * 設置文字水印
     * @param bos
     * @param input
     * @param text
     * @throws DocumentException
     * @throws IOException
     */
    public static void setWatermarkText(BufferedOutputStream bos, String input,String text)
            throws DocumentException, IOException {
        PdfReader reader = new PdfReader(input);
        PdfStamper stamper = new PdfStamper(reader, bos);
        int total = reader.getNumberOfPages()+1;
        PdfContentByte content;
        BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        PdfGState gs1 = new PdfGState();
        gs1.setFillOpacity(0.2f);//設置透明度
        PdfGState gs2 = new PdfGState();
        gs2.setFillOpacity(1f);
        for (int i = 1; i < total; i++) {
            content = stamper.getOverContent(i);// 在內容上方加水印
            //content = stamper.getUnderContent(i);//在內容下方加水印
            //水印內容
            content.setGState(gs1);
            content.beginText();
            content.setColorFill(Color.GRAY);
            content.setFontAndSize(base, 50);
            content.setTextMatrix(70, 200);
            //350為x坐標 350y坐標  45為旋轉45度
            content.showTextAligned(Element.ALIGN_CENTER, text, 350, 350, 45);
            content.endText();//結束文字
            //頁腳內容
            content.setGState(gs2);
            content.beginText();
            content.setColorFill(Color.BLACK);
            content.setFontAndSize(base, 8);
            content.setTextMatrix(70, 200);
            content.showTextAligned(Element.ALIGN_CENTER, "壹新設計報價雲平台 www.newbeall.com 第"+i+ "頁,共"+(total-1)+"頁", 370, 10, 0);
            content.endText();
        }
        stamper.close();
    }
    
    /**
     * 設置圖片水印
     * @param bos輸出文件的位置
     * @param input
     *            原PDF位置
     * @param waterMarkName
     *            頁腳添加水印
     * @param permission
     *            權限碼
     * @throws DocumentException
     * @throws IOException
     */
    public static void setWatermarkImage(BufferedOutputStream bos, String input, String waterMarkName, int permission)
            throws DocumentException, IOException {
    	
        
        PdfReader reader = new PdfReader(input);
        PdfStamper stamper = new PdfStamper(reader, bos);
        
        int total = reader.getNumberOfPages() + 1;
        PdfContentByte content;
        BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        PdfGState gs = new PdfGState();
        for (int i = 1; i < total; i++) {
            content = stamper.getOverContent(i);// 在內容上方加水印
            // content = stamper.getUnderContent(i);//在內容下方加水印
            gs.setFillOpacity(0.2f);
            // content.setGState(gs);
            content.beginText();
            content.setColorFill(Color.LIGHT_GRAY);
            content.setFontAndSize(base, 50);
            content.setTextMatrix(70, 200);
            //這里的水印圖片換成你自己的
            Image image = Image.getInstance("C:\\Users\\Administrator\\Desktop\\quotation12.png");
            /*
              img.setAlignment(Image.LEFT | Image.TEXTWRAP);
              img.setBorder(Image.BOX); img.setBorderWidth(10);
              img.setBorderColor(BaseColor.WHITE); img.scaleToFit(100072);//大小
              img.setRotationDegrees(-30);//旋轉
             */
            image.setAbsolutePosition(200, 206); // set the first background
            image.scaleToFit(200, 200);// image of the absolute
            image.setRotationDegrees(45);//旋轉45度
            content.addImage(image);
            content.setColorFill(Color.BLACK);
            content.setFontAndSize(base, 8);
            content.showTextAligned(Element.ALIGN_CENTER, "下載時間:" + waterMarkName + "", 300, 10, 0);
            content.endText();

        }
        stamper.close();
    }
}

參考

java生成pdf

wkhtmltopdf 參數 詳解

Java給圖片和PDF文件添加水印(圖片水印和文字水印)

maven項目中使用itextasian

使用 wkhtmltopdf 導出時遇到的問題


免責聲明!

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



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