公司項目有個需求就是根據模板動態的生成pdf文件,在網上看過很多方式生成的效果都不是很好。要么樣式不支持,要么字體不支持。我這邊項目的需求是這樣:
1.根據模板生成紙張方向為橫向的pdf
2.給pdf的每一頁中間的位置都加上文字水印
研究了很久終於實現了效果,效果如下圖所示:
總體實現方案
-
准備好html模板 --->就是寫html,注意:樣式少用css3的,並且不要使用外部樣式.可以把樣式寫在style標簽內部,也就是保證樣式和html標簽在同一個html文件中。
-
通過模板引擎將html模板生成含數據的html ---> 使用
beetl
模板引擎實現,當然你也可以用freemarker
等模板引擎實現 -
把生成的html轉成pdf--->通過
wkhtmltopdf
這個程序實現 -
最后把生成的pdf文件加上水印--->通過
itext
實現
1 編寫模板
模板如下,其實就是寫html文件,可以看到${xxx}
這種占位符,這些數據都是動態的,用來給模板引擎識別的。
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
根據自己的需要下載相應的版本即可
3.2 安裝wkhtmltopdf
下載下來的安裝包如下:
3.2.1 windows安裝
windows安裝比較簡單,一直點擊下一步即可
裝好之后可以使用cmd命令行來運行
在裝好的路徑下右鍵--->打開命令窗口:
命令格式:
wkhtmltopdf.exe https://wkhtmltopdf.org/downloads.html c:\1.pdf
可以看到其實就是把https://wkhtmltopdf.org/downloads.html這個網頁在c盤下生成1.pdf
當然也可以把本地的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'
比如我查出來的結果如下:
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文件夾下面即可
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();
}
}