目錄
前言
一、如何后臺生成Echarts圖片?
1.PhantomJS
2.PhantomJS的下載
?3.用phantomjs調(diào)用echarts-converts.js生成圖片
二、Java如何將Echarts圖生成到PDF
1.生成PDF依賴
2.Java代碼測試例子:
?3.測試結(jié)果?
?三、下載生成的PDF
ReportFormUtil
前言
提示:本文僅用于記錄日常,多有不足,僅供參考。
本次任務:要求不經(jīng)過web頁面,Java如何按月定時生成含有Echarts圖的PDF。
與我之前一篇文章中介紹的(Java如何根據(jù)前臺Echarts圖表生成PDF,并下載)區(qū)別在于,該文章中的Echarts圖片可以從已有的web頁面獲取,而本次任務沒有頁面。Echarts圖需要由后臺生成。那么整個流程分為以下三步:
1.如何后臺生成Echarts圖片?
2.Java如何將Echarts圖生成到PDF?
3.下載生成的PDF
本次任務重點在于第一點,只要后臺能生成Echarts圖片,生成PDF和下載文件的過程與前面文章中提到的方法差不多。
一、如何后臺生成Echarts圖片?
1.PhantomJS
后臺生成Echarts圖,需要使用到PhantomJS:一個自帶JavaScript API的無頭WebKit腳本,簡單理解就是:它能干瀏覽器能干的幾乎所有事情,能解析js、能渲染等等,但是沒有頁面讓你看。
2.PhantomJS的下載
https://phantomjs.org/api/fs/
http://wenku.kuryun.com/docs/phantomjs/index.html
也可以直接用我下載的文件:我的文件鏈接? 提取碼:es4e
打開我的文件:根據(jù)你使用的操作系統(tǒng),選擇一個進行下載并解壓。
?
?打開我的文件,除phantomjs文件外,可見還有一個文件夾echarts-convert,有2個文件:
1.echarts:里面是echarts和jquery,可以用你們自己本地或項目中的。
2.echarts-converts.js:是自己寫的js文件,用來渲染生成echarts圖片,可根據(jù)自己需求改寫。
?
注意:若使用自己本地的echarts和jquery文件,echarts-converts.js里面的文件路徑需要改寫,指向你的文件所在的位置。
?3.用phantomjs調(diào)用echarts-converts.js生成圖片
以下以windows環(huán)境下舉例:?
1.從echarts官網(wǎng)隨便選擇一個圖,將option復制本地某個文件中(例:G:\test\testOption.txt)
2.手動創(chuàng)建一個空的png文件(例:G:\test\111.png)
? ? ????
3.cmd調(diào)用phantomjs進程,讓它去解析echarts-converts.js,并傳入一些參數(shù)(如圖):
G:\test\phantomjs-2.1.1-windows\bin\phantomjs.exe G:\test\echarts-convert\echarts-convert.js -txtPath G:\test\testOption.txt -picTmpPath G:\test\111.png -picPath G:\test\222.png
??
?
4.執(zhí)行完成,完成后,111.png圖片有內(nèi)容,且生成了一張222.png。
?
二、Java如何將Echarts圖生成到PDF
前面已經(jīng)可以通過手動調(diào)用的方式生成Echarts圖片。
接下來需要做的:
1、將手動生成圖片的過程通過代碼實現(xiàn)。
2、將生成的圖片生成出PDF
3、下載PDF
1.生成PDF依賴
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
2.Java代碼測試例子:
public static void main(String[] args)
{
try
{
// 測試同時生成兩張圖
// 注意:要先手動創(chuàng)建兩個空文件G:\\test\\1.png和G:\\test\\2.png;要提前將echarts的option數(shù)據(jù)寫到G:\\test\\testOption.txt和G:\\test\\testOption2.txt中
doExecPhantomJS_deleteFileAfterException("G:\\test\\testOption.txt,G:\\test\\testOption2.txt", "G:\\test\\1.png,G:\\test\\2.png", "G:\\test\\111.png,G:\\test\\222.png");
}
catch (Exception e)
{
e.printStackTrace();
}
Paragraph ph1 = ReportFormUtil.createImageParagraphByPath("1、段落標題111", "G:\\test\\111.png", 35, 35,"這是一段前綴描述信息111", "這是一段后綴描述信息111");
Paragraph ph2 = ReportFormUtil.createImageParagraphByPath("2、段落標題222", "G:\\test\\222.png", 35, 35, "這是一段前綴描述信息222", "這是一段后綴描述信息222");
List<Paragraph> phs = new ArrayList<Paragraph>();
phs.add(ph1);
phs.add(ph2);
ReportFormUtil.createPDFDocumentToDisk("封面名稱", "小標題", "", phs, "G:\\test\\document.pdf");
}
?Tips:
一、ReportFormUtil文件放在文章結(jié)尾。
二、關(guān)于如何自動生成默認樣式的option.txt文件,不過多贅述:
我的主要做法是:option配置可以看成一個json格式的字符串,然后:
1、提供可配置的默認的option樣式;
2、將option中的數(shù)據(jù)部分(想自定義的部分)用符號占位;
3、在需要生成圖片時,結(jié)合業(yè)務計算出自定義數(shù)據(jù)并轉(zhuǎn)成json,替換掉占位符填到option字符串中生成完整的帶數(shù)據(jù)的option字符串。
4、將3中生成的完整option字符串寫到指定臨時txt文件中(流程結(jié)束后刪除臨時文件)
3.測試結(jié)果?
測試結(jié)果如圖所示:成功生成pdf文件,且生成了兩個圖片段落
文章來源:http://www.zghlxwxcb.cn/news/detail-698539.html
?三、下載生成的PDF
@Override
public void download(HttpServletResponse response, HttpServletRequest request) throws BusinessException
{
// 選中的要導出的文檔id
String id = request.getParameter("id");
if (StringUtils.isBlank(id))
{
return;
}
// 1.根據(jù)id找出文檔信息
DocumentTaskModel taskModel = documentTaskDao.findById(Integer.valueOf(id));
// 2.下載
File file = new File(taskModel.getUrl());// taskModel.getUrl():該PDF文件的存放路徑
try (FileInputStream in = new FileInputStream(file); ServletOutputStream out = response.getOutputStream();)
{
// 若未定義文件名,則使用文檔配置中的默認文檔名
String fileName = StringUtils.isNotBlank(taskModel.getName()) ? taskModel.getName()
: ReportFormEngine.getDocumentByKey(taskModel.getDocumentKey()).getName();
response.reset();
response.setContentType("application/x-msdownload");
response.setHeader("Content-Length", "" + file.length());
response.addHeader("Content-Disposition",
"attachment; filename=" + new String((fileName + ".pdf").getBytes("utf-8"), "iso8859-1"));
byte[] buff = new byte[CommonConstants.BYTE_BUFFER_LENGTH_5120];
int hasRead = -1;
while ((hasRead = in.read(buff, 0, buff.length)) > 0)
{
out.write(buff, 0, hasRead);
}
}
catch (FileNotFoundException e)
{
logger.error(e);
}
catch (IOException e)
{
logger.error(e);
}
catch (Exception e)
{
logger.error(taskModel.getUrl() + "下載文件失?。?, e);
}
}
ReportFormUtil
package com.smartsecuri.bp.cbb.reportform;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.smartsecuri.bp.cbb.exception.BusinessException;
import com.smartsecuri.bp.cbb.utils.file.FileUtils;
import com.smartsecuri.bp.cbb.utils.other.CommonUtils;
import com.smartsecuri.bp.cbb.utils.system.IoUtils;
/**
* ClassName : ReportFormUtil <br/>
* Function : (PDF文檔相關(guān)-主要用于生成報表) <br/>
* date : 2022年11月29日 下午5:32:18 <br/>
*
* @author
* @version
* @since JDK 1.8
*/
public class ReportFormUtil
{
private static final Logger logger = LogManager.getLogger(ReportFormUtil.class);
/**
* DEFAULT_LEADING : (默認行間距).
*/
public static final float DEFAULT_LEADING = 16f;
/**
* fontSize_normal : (正文字體大小11號).
*/
public static final float FONTSIZE_NORMAL = 11f;
/**
* fontSize_titile : (標題字體大小14號).
*/
public static final float FONTSIZE_TITILE = 14f;
/**
* FONTSIZE_COVER : (封面字體大小32號).
*/
public static final float FONTSIZE_COVER = 32f;
/**
* normalFont : (通用字體樣式:宋體、11號).
*/
private static Font normalFont = null;
/**
* titleFont : (通用標題字體樣式:宋體、14號、加粗).
*/
private static Font titleFont = null;
/**
* coverFont : (通用封面字體樣式:宋體、28號、加粗).
*/
private static Font coverFont = null;
// windows 測試環(huán)境
private static final String phantomPath = "G:\\test\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe";
private static final String JSpath = "G:\\test\\echarts-convert\\echarts-convert.js";
// linux 環(huán)境
// private static String phantomPath = "/usr/local/phantomjs/bin/phantomjs";
// private static String JSpath = PathUtils.getProjectPath() + "common/reportform/echarts-convert.js";
/**
* getBaseFont : (獲取可以解析中文的字體:使用宋體). <br/>
*
* @author
* @return
* @since JDK 1.8
*/
public static BaseFont getBaseFontChinese()
{
try
{
// 宋體資源文件路徑
URL path = ReportFormUtil.class.getResource("/config/fonts/simsun.ttc");
return BaseFont.createFont(path + ",0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 本地main方法測試:使用windows自帶的宋體文件
//return BaseFont.createFont("C://Windows//Fonts//simsun.ttc,0", BaseFont.IDENTITY_H, false);
}
catch (Exception e)
{
logger.error("設(shè)置字體樣式失敗", e);
return null;
}
}
/**
* getNormalFont : (獲取普通正文字體樣式). <br/>
*
* @author
* @return
* @since JDK 1.8
*/
public static Font getNormalFont()
{
if (normalFont == null)
{
BaseFont bfChinese = getBaseFontChinese();
normalFont = new Font(bfChinese, FONTSIZE_NORMAL, Font.NORMAL);
}
return normalFont;
}
/**
* getTitleFont : (獲取標題通用字體). <br/>
*
* @author
* @return
* @since JDK 1.8
*/
public static Font getTitleFont()
{
if (titleFont == null)
{
BaseFont bfChinese = getBaseFontChinese();
titleFont = new Font(bfChinese, FONTSIZE_TITILE, Font.BOLD);
}
return titleFont;
}
/**
* getTitleFont : (獲取封面通用字體). <br/>
*
* @author
* @return
* @since JDK 1.8
*/
public static Font getCoverFontFont()
{
if (coverFont == null)
{
BaseFont bfChinese = getBaseFontChinese();
coverFont = new Font(bfChinese, FONTSIZE_COVER, Font.BOLD);
}
return coverFont;
}
/**
* genFrontCover : (構(gòu)建封面的文字和樣式). <br/>
*
* @author
* @param coverName 封面標題
* @param subtitle 小標題——封面標題下一行文字,可以為null或空串,表示不填
* @param subscript 下標,可以為null或空串,表示不填
* @return
* @since JDK 1.8
*/
public static Paragraph genFrontCover(String coverName, String subtitle, String subscript)
{
if (StringUtils.isBlank(coverName))
{
return null;
}
// 生成封面
Paragraph frontCover = new Paragraph();
frontCover.setAlignment(Element.ALIGN_CENTER);
// 空10行
ReportFormUtil.addBlankLine(frontCover, Integer.parseInt("10"));
// 封面標題
frontCover.add(new Chunk(coverName, ReportFormUtil.getCoverFontFont()));
if (StringUtils.isNotBlank(subtitle))
{
ReportFormUtil.addBlankLine(frontCover, Integer.parseInt("2"));// 換行
// 小標題
frontCover.add(new Chunk(subtitle, ReportFormUtil.getTitleFont()));
}
if (StringUtils.isNotBlank(subscript))
{
// 換行
ReportFormUtil.addBlankLine(frontCover, Integer.parseInt("25"));
// companyName公司簽名如:"慧盾信息安全科技(蘇州)股份有限公司"
frontCover.add(new Chunk(subscript, ReportFormUtil.getNormalFont()));
}
return frontCover;
}
/**
* addBlankLine : (添加換行). <br/>
*
* @author
* @param paragraph 需要添加空行的段落
* @param lineNum 需要添加空行的個數(shù)
* @since JDK 1.8
*/
public static void addBlankLine(Paragraph paragraph, int lineNum)
{
if (paragraph == null)
{
return;
}
for (int i = 0; i < lineNum; i++)
{
paragraph.add(Chunk.NEWLINE);
}
}
/**
* createTable : (創(chuàng)建table段落). <br/>
*
* @author
* @param <T>
* @param list 構(gòu)建table的數(shù)據(jù)
* @param title 該段落取的名字,標題默認居左
* @param methodNames 需要調(diào)用的方法名,用來獲取單元格數(shù)據(jù)。通常是某個屬性的get方法
* @param prefixDescribe 前綴附加文字描述
* @param suffixDescribe 后綴附加文字描述
* @return
* @since JDK 1.8
*/
public static <T> Paragraph createTable(List<T> list, String title, String[] tableHead, String[] methodNames,
String prefixDescribe, String suffixDescribe)
{
return createTable(list, FONTSIZE_NORMAL, FONTSIZE_TITILE, title, tableHead, methodNames,
prefixDescribe, suffixDescribe);
}
/**
* createTableByList : (創(chuàng)建table段落). <br/>
*
* @author
* @param <T>
* @param listData
* @param normalFontSize 正文字體大小
* @param titleFontSize 標題字體大小
* @param title 段落名稱
* @param methodNames 獲取表格屬性的方法名
* @param prefixDescribe 前綴附加文字描述
* @param suffixDescribe 后綴附加文字描述
* @return
* @since JDK 1.8
*/
public static <T> Paragraph createTable(List<T> listData, float normalFontSize, float titleFontSize, String title,
String[] tableHead, String[] methodNames, String prefixDescribe, String suffixDescribe)
{
// 1.創(chuàng)建一個表格
PdfPTable table = new PdfPTable(methodNames.length);// 列數(shù)
// 2.構(gòu)造表頭
for (String head : tableHead)
{
head = StringUtils.isBlank(head) ? "" : head;
PdfPCell cell = new PdfPCell(new Phrase(head, getNormalFont()));
cell.setBackgroundColor(
new BaseColor(Integer.parseInt("124"), Integer.parseInt("185"), Integer.parseInt("252")));// 背景色
cell.setMinimumHeight(Float.parseFloat("15"));// 單元格最小高度
cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
cell.setVerticalAlignment(Element.ALIGN_CENTER);// 垂直居中
table.addCell(cell);
}
// 3.構(gòu)造table數(shù)據(jù)
if (CollectionUtils.isEmpty(listData))
{
// 沒有數(shù)據(jù),添加一行空單元格,并返回
for (int i = 0; i < methodNames.length; i++)
{
table.addCell(new Phrase(" "));// 有一個空格,否則添加不了
}
}
else
{
// 有數(shù)據(jù):構(gòu)造table數(shù)據(jù)
for (T li : listData)
{
for (String name : methodNames)
{
Object obj = CommonUtils.invokeMethod(li, name);
String valueStr = obj == null ? " " : StringUtils.isEmpty(obj.toString()) ? " " : obj.toString();
PdfPCell cell = new PdfPCell(new Phrase(valueStr, getNormalFont()));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
table.addCell(cell);
}
}
}
// 4.返回段落
return createParagraph(table, title, prefixDescribe, suffixDescribe);
}
/**
* addDataToTable : (從段落中找到table元素,向該table中追加數(shù)據(jù)). <br/>
*
* @author
* @param <T>
* @param paragraph table表格段落
* @param listData 需要追加的數(shù)據(jù)
* @param methodNames 每個數(shù)據(jù)獲取的方法名
* @since JDK 1.8
*/
public static <T> void addDataToTable(Paragraph paragraph, List<T> listData, List<String> methodNames)
{
for (Element ele : paragraph)
{
if (!(ele instanceof PdfPTable))
{
// 不是table元素,直接跳過
continue;
}
// 找到第一個table元素
PdfPTable table = (PdfPTable) ele;
for (T data : listData)
{
for (String name : methodNames)
{
String valueStr = CommonUtils.invokeMethod(data, name).toString();
PdfPCell cell = new PdfPCell(new Phrase(valueStr, getNormalFont()));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 單元格文字水平居中
table.addCell(cell);
}
}
break;
}
}
/**
* createImage : (根據(jù)圖片的base64加密文件創(chuàng)建pdf圖片段落). <br/>
*
* @author
* @param picBase64Info 傳入圖片的base64信息,或傳入前臺echart通過調(diào)用getDataURL()方法獲取的圖片信息都可以
* @param title 段落標題
* @param percentX 圖片縮放比例X軸
* @param percentY 圖片縮放比例Y軸
* @param prefixDescribe 前綴附加文字描述
* @param suffixDescribe 后綴附加文字描述
*
* @return 返回圖片段落
* @since JDK 1.8
*/
public static Paragraph createImageFromEncodeBase64(String picBase64Info, String title, float percentX,
float percentY, String prefixDescribe, String suffixDescribe)
{
// 1.獲取圖片
Element element = analysisPicBase64Info(picBase64Info);
// 2.創(chuàng)建段落,并添加標題,設(shè)置縮放
return createImageParagraph(element, title, percentX, percentY, prefixDescribe, suffixDescribe);
}
/**
* createImageFromEncodeBase64_batch : (批量創(chuàng)建圖片段落). <br/>
*
* @author
* @param picBase64Infos 傳入圖片的base64信息,或傳入前臺echart通過調(diào)用getDataURL()方法獲取的圖片信息都可以
* @param titles 每個段落的標題
* @param percentXs X軸縮放比例
* @param percentYs Y軸縮放比例
* @param titleCenter 標題是否居中,true-居中、false-默認居左
* @return 返回由多個圖片段落組合后的整個段落
* @since JDK 1.8
*/
public static Paragraph createImageFromEncodeBase64_batch(List<String> picBase64Infos, List<String> titles,
List<Float> percentXs, List<Float> percentYs)
{
Paragraph paragraphs = new Paragraph(DEFAULT_LEADING);
for (int i = 0; i <= picBase64Infos.size(); i++)
{
Paragraph imagePara = createImageFromEncodeBase64(picBase64Infos.get(i), titles.get(i), percentXs.get(i),
percentYs.get(i), null, null);
if (!imagePara.isEmpty())
{
paragraphs.add(imagePara);
// 換行
paragraphs.add(Chunk.NEWLINE);
}
}
return paragraphs;
}
/**
* createPicParagraphByPath : (根據(jù)圖片位置生成圖片段落段落). <br/>
*
* @author
* @param title 段落標題
* @param picPath 圖片所在磁盤路徑
* @param percentX 圖片縮放比例X軸
* @param percentY 圖片縮放比例Y軸
* @param titleCenter 標題是否居中,true-居中、false-默認居左
* @param prefixDescribe 前綴附加文字描述
* @param suffixDescribe 后綴附加文字描述
* @return
* @since JDK 1.8
*/
public static Paragraph createImageParagraphByPath(String title, String picPath, float percentX, float percentY,
String prefixDescribe, String suffixDescribe)
{
// 1.獲取圖片
Element element = analysisPicByPath(picPath);
// 2.創(chuàng)建段落,并添加標題,設(shè)置縮放
return createImageParagraph(element, title, percentX, percentY, prefixDescribe, suffixDescribe);
}
/**
* createPicParagraphByPath : (根據(jù)圖片位置生成圖片段落段落). <br/>
*
* @author
* @param picElement 圖片元素
* @param title 段落標題
* @param percentX 圖片縮放比例X軸
* @param percentY 圖片縮放比例Y軸
* @param prefixDescribe 前綴附加文字描述
* @param suffixDescribe 后綴附加文字描述
* @return
* @since JDK 1.8
*/
public static Paragraph createImageParagraph(Element picElement, String title, float percentX, float percentY,
String prefixDescribe, String suffixDescribe)
{
if (picElement == null)
{
return new Paragraph();
}
try
{
if (!(picElement instanceof Image))
{
// 1. 圖片解析失敗
logger.error(title + ":picElement is not instanceof Image");
return new Paragraph();
}
// 2.設(shè)置圖片縮放比例
Image image = (Image) picElement;
image.scalePercent(percentX, percentY);
image.setAlignment(Element.ALIGN_CENTER);
// 3.創(chuàng)建并返回圖片段落
return createParagraph(image, title, prefixDescribe, suffixDescribe);
}
catch (Exception e)
{
logger.error(e);
// 空段落
return new Paragraph();
}
}
/**
* createTxtParagraph : (創(chuàng)建文本段落). <br/>
*
* @author
* @param strings 多行句子
* @param title 段落標題
* @param titleCenter 標題居中
* @param prefixDescribe 前綴附加文字描述
* @param suffixDescribe 后綴附加文字描述
* @return
* @since JDK 1.8
*/
public static Paragraph createTxtParagraph(List<String> strings, String title, String prefixDescribe,
String suffixDescribe)
{
Phrase phrase = new Phrase();
for (String li : strings)
{
// 多行句子拼裝
phrase.add(new Chunk(li, getNormalFont()));
phrase.add(Chunk.NEWLINE);
}
return createParagraph(phrase, title, prefixDescribe, suffixDescribe);
}
/**
* createParagraph : (根據(jù)元素創(chuàng)建段落). <br/>
*
* @author
* @param element
* @param title 段落標題
* @param prefixDescribe 前綴附加文字描述
* @param suffixDescribe 后綴附加文字描述
* @return
* @since JDK 1.8
*/
public static Paragraph createParagraph(Element element, String title, String prefixDescribe,
String suffixDescribe)
{
title = StringUtils.isEmpty(title) ? "" : title;
try
{
// 1.創(chuàng)建段落,并添加標題,添加前綴描述
Paragraph paragraph = createParagraph(title, prefixDescribe);
paragraph.add(element);
// 2.后綴描述
if (StringUtils.isNotBlank(suffixDescribe))
{
addBlankLine(paragraph, 1);// 換行符
paragraph.add(new Paragraph(DEFAULT_LEADING, suffixDescribe, getNormalFont()));
}
return paragraph;
}
catch (Exception e)
{
logger.error(e);
// 空段落
return new Paragraph();
}
}
/**
* createParagraph : (創(chuàng)建段落). <br/>
*
* @author
* @param title
* @param prefixDescribe 前綴附加文字描述
* @return
* @since JDK 1.8
*/
public static Paragraph createParagraph(String title, String prefixDescribe)
{
Paragraph paragraph = new Paragraph(DEFAULT_LEADING);
if (StringUtils.isNotEmpty(title))
{
paragraph.add(new Phrase(DEFAULT_LEADING, title, getTitleFont()));
addBlankLine(paragraph, 2);
}
// 2.前綴描述
if (StringUtils.isNotBlank(prefixDescribe))
{
paragraph.add(new Paragraph(prefixDescribe, getNormalFont()));
ReportFormUtil.addBlankLine(paragraph, Integer.parseInt("1"));// 換行
}
return paragraph;
}
/**
* createPDFDocument : (創(chuàng)建文檔,默認紙張大小A4). <br/>
*
* @author WuTingTing
* @return
* @since JDK 1.8
*/
public static Document createPDFDocument()
{
return new Document(PageSize.A4, Float.parseFloat("36"), Float.parseFloat("36"), Float.parseFloat("36"),
Float.parseFloat("36"));
}
/**
* createPDFDocumentToDisk : (生成PDF文檔保存到磁盤). <br/>
*
* @author WuTingTing
* @param coverName 封面標題
* @param subtitle 小標題——封面標題下一行文字,可以為null或空串,表示不填
* @param subscript 下標,可以為null或空串,表示不填
* @param paragraphs 段落
* @param documentPath 文件保存全路徑
* @return
* @since JDK 1.8
*/
public static boolean createPDFDocumentToDisk(String coverName, String subtitle, String subscript,
List<Paragraph> paragraphs, String documentPath)
{
// 1.創(chuàng)建文檔,設(shè)置文檔頁面大小,頁邊距
Document document = createPDFDocument();
// 2.封面
Paragraph cover = genFrontCover(coverName, subtitle, subscript);
// 3.生成文檔并保存到指定路徑
return downloadDocument(document, cover, paragraphs, documentPath);
}
/**
* exportDocument : (生成并下載PDF文檔-通過response響應實現(xiàn)下載). <br/>
*
* @author
* @param document 文檔對象
* @param cover 封面:若不是null,則會先添加封面,并另起新頁面添加段落;若是null表示沒有封面。
* @param paragraphs 需要組成PDF文件的各個段落
* @param response 請求的響應對象(提示:前臺無法通過ajax請求觸發(fā)瀏覽器的下載,可以通過表單提交的方式)
* @param fileName 生成的文件名稱,不需要加pdf后綴
* @since JDK 1.8
*/
public static void exportDocument(Document document, Paragraph cover, List<Paragraph> paragraphs,
HttpServletResponse response, String fileName)
{
try (ServletOutputStream out = response.getOutputStream())
{
response.setContentType("application/binary;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName + ".pdf", "UTF-8"));
PdfWriter writer = PdfWriter.getInstance(document, out);
writer.setStrictImageSequence(true);// 設(shè)置圖片位置精確放置
// 1.打開文檔
document.open();
if (cover != null)
{
// 2.有封面:添加封面,并另起一頁,用來塞后面的段落
document.add(cover);
document.newPage(); // 另起一頁
}
StringBuilder errorMsg = new StringBuilder();
for (int i = 0; i < paragraphs.size(); i++)
{
try
{
// 將段落添加到文檔
document.add(paragraphs.get(i));
// 換行
document.add(Chunk.NEWLINE);
}
catch (DocumentException e)
{
errorMsg.append("PDF文件生成出錯,請檢查第:").append(i).append("個段落");
}
}
if (!StringUtils.isEmpty(errorMsg.toString()))
{
logger.error(errorMsg);
}
// 關(guān)閉文檔
document.close();
// 將數(shù)據(jù)輸出
out.flush();
out.close();
}
catch (IOException e)
{
logger.error("生成PDF文檔并下載,IOException:", e);
}
catch (DocumentException e)
{
logger.error("生成PDF文檔并下載,DocumentException:", e);
}
finally
{
document.close();
}
}
/**
* downloadDocument : (生成PDF文檔并保存到磁盤). <br/>
*
* @author
* @param document 文檔對象
* @param cover 封面:若不是null,則會先添加封面,并另起新頁面添加段落
* @param paragraphs 需要組成PDF文件的段落
* @param response 請求的響應對象
* @param fileName 生成的文件名稱,不需要加pdf后綴
* @return true成功、false失敗
* @since JDK 1.8
*/
public static boolean downloadDocument(Document document, Paragraph cover, List<Paragraph> paragraphs,
String documentPath)
{
FileOutputStream out = null;
try
{
File file = new File(documentPath);
if (!FileUtils.createFile(file))
{
return false;
}
out = new FileOutputStream(file);
PdfWriter writer = PdfWriter.getInstance(document, out);
writer.setStrictImageSequence(true);// 設(shè)置圖片位置精確放置
// 打開文檔
document.open();
if (cover != null)
{
document.add(cover);
// 起新頁面
document.newPage();
}
StringBuilder errorMsg = new StringBuilder();
for (int i = 0; i < paragraphs.size(); i++)
{
try
{
// 將段落添加到文檔
document.add(paragraphs.get(i));
// 換行
document.add(Chunk.NEWLINE);
}
catch (DocumentException e)
{
errorMsg.append("PDF文件生成出錯,請檢查第:").append(i).append("個段落");
}
}
if (!StringUtils.isBlank(errorMsg.toString()))
{
logger.error(errorMsg);
}
// 關(guān)閉文檔
document.close();
out.flush();
IoUtils.close(out);
}
catch (Exception e)
{
logger.error("生成PDF文檔并下載,出錯:", e);
return false;
}
finally
{
// 關(guān)閉文檔
document.close();
IoUtils.close(out);
}
return true;
}
/**
* analysisPicBase64Info : (解析base64圖片信息). <br/>
*
* @author
* @param picBase64Info 傳入圖片的base64信息,或傳入前臺echart通過調(diào)用getDataURL()方法獲取的圖片信息都可以
* @return 圖片經(jīng)過base64解碼后的信息
* @since JDK 1.8
*/
public static Element analysisPicBase64Info(String picBase64Info)
{
if (StringUtils.isEmpty(picBase64Info))
{
// 空段落
return new Paragraph();
}
// 1.獲取圖片base64字符串信息:若入?yún)⑹峭ㄟ^前臺echarts調(diào)用getDataURL()方法獲取的,則該字符串包含逗號,且則逗號后面的內(nèi)容才是圖片的信息
String pictureInfoStr = picBase64Info.indexOf(",") == -1 ? picBase64Info : picBase64Info.split(",")[1];
// 2.將圖片信息進行base64解密
byte[] imgByte = Base64.decodeBase64(pictureInfoStr);
// 對異常的數(shù)據(jù)進行處理
/**
* .圖片的原始表達ascii碼范圍是0-255,
* .這里面有一些不可見的編碼。然后為了圖片正確傳輸才轉(zhuǎn)成編碼base64的0-63,
* .當從base64轉(zhuǎn)成byte時,byte的范圍是[-128,127],那么此時就會可能產(chǎn)生負數(shù),而負數(shù)不是在ascii的范圍里,所以需要轉(zhuǎn)換一下
*/
for (int i = 0; i < imgByte.length; i++)
{
if (imgByte[i] < 0)
{
imgByte[i] += 256;
}
}
try
{
return Image.getInstance(imgByte);
}
catch (Exception e)
{
logger.error("analysisPicBase64Info error", e);
return new Paragraph();
}
}
/**
* analysisPicBase64Info : (根據(jù)圖片地址解析并生成圖片段落). <br/>
*
* @author
* @param picPath 圖片路徑
* @return
* @since JDK 1.8
*/
public static Element analysisPicByPath(String picPath)
{
if (StringUtils.isEmpty(picPath))
{
return null;// 空段落
}
File file = new File(picPath);
if (!file.exists())
{
// 圖片文件不存在
return null;// 空段落
}
try (FileInputStream in = new FileInputStream(file))
{
byte[] imgByte = new byte[(int) file.length()];
in.read(imgByte);
/**
* .圖片的原始表達ascii碼范圍是0-255,
* .這里面有一些不可見的編碼。然后為了圖片正確傳輸才轉(zhuǎn)成編碼base64的0-63,
* .當從base64轉(zhuǎn)成byte時,byte的范圍是[-128,127],那么此時就會可能產(chǎn)生負數(shù),而負數(shù)不是在ascii的范圍里,所以需要轉(zhuǎn)換一下
*/
for (int i = 0; i < imgByte.length; i++)
{
if (imgByte[i] < 0)
{
imgByte[i] += 256;
}
}
return Image.getInstance(imgByte);
}
catch (Exception e)
{
logger.error("analysisPicBase64Info error", e);
return null;
}
}
/**
* analysisPicBase64Info_batch : (批量解析base64加密的圖片信息,生成Image對象). <br/>
*
* @author
* @param picBase64Infos 傳入圖片的base64信息,或傳入前臺echart通過調(diào)用getDataURL()方法獲取的圖片信息都可以
* @return
* @since JDK 1.8
*/
public static List<Element> analysisPicBase64Info_batch(List<String> picBase64Infos)
{
List<Element> images = new ArrayList<Element>();
for (String li : picBase64Infos)
{
Element image = analysisPicBase64Info(li);
images.add(image);
}
return images;
}
/**
* doExecPhantomJS_deleteFileAfterException : (執(zhí)行:瀏覽器加載option,渲染并截圖保存到制定文件,若過程中出現(xiàn)錯誤則刪除picTmpPath、picPath中生成的文件). <br/>
* .【注意】支持一次多個,多個時路徑以逗號隔開,且jsonPath和picPath的個數(shù)要一致。
*/
public static void doExecPhantomJS_deleteFileAfterException(String txtPath, String picTmpPath, String picPath)
throws BusinessException, Exception
{
doExecPhantomJS_deleteFileAfterException(txtPath, picTmpPath, picPath, null, null);
}
/**
* doExecPhantomJS_deleteFileAfterException : (執(zhí)行:瀏覽器加載option,渲染并截圖保存到制定文件,若過程中出現(xiàn)錯誤則刪除picTmpPath、picPath中生成的文件). <br/>
* .【注意】支持一次多個,多個時路徑以逗號隔開,且jsonPath和picPath的個數(shù)要一致。
* .單個例子:
* txtPath="/a/a.json"
* picTmpPath="/a/tmp/a.png"
* picPath="/a/a.png"
* width=1000
* height=600
*
* .多個例子:
* txtPath="/a/a.txt,/b/b.json,/c/c.txt"
* picTmpPath="/a/tmp/a.png,/b/tmp/b.png,/c/tmp/c.png"
* picPath="/a/a.png,/b/b.png,/c/c.png"
* width=1000,1000,1000
* height=600,600,600,600
*
* @author WuTingTing
* @param txtPath 圖表option信息的txt文件全路徑
* @param picTmpPath 圖片臨時存放路徑,生成后將轉(zhuǎn)移到picPath路徑下
* @param picPath 圖片文件全路徑
* @param width 自定義截圖寬度
* @param height 自定義截圖高度
* @throws Exception
* @since JDK 1.8
*/
public static void doExecPhantomJS_deleteFileAfterException(String txtPath, String picTmpPath, String picPath,
String width, String height) throws BusinessException, Exception
{
try
{
doExecPhantomJS(txtPath, picTmpPath, picPath, width, height);
}
catch (Exception e)
{
// 執(zhí)行過程中出錯,刪除本次執(zhí)行中可能生成的文件,不刪除txtPath是防止該文件為默認內(nèi)置文件
String[] picTmpPaths = picTmpPath.split(",");
String[] picPaths = picPath.split(",");
for (String li : picTmpPaths)
{
FileUtils.delFile(li);
}
for (String li : picPaths)
{
FileUtils.delFile(li);
}
if (e instanceof BusinessException)
{
logger.error(((BusinessException) e).getMsg());
throw new BusinessException().setMsg(((BusinessException) e).getMsg());
}
else
{
throw new Exception(e);
}
}
}
/**
* doExecPhantomJS : (執(zhí)行:瀏覽器加載option,渲染并截圖保存到制定文件). <br/>
* .【注意】支持一次多個,多個時路徑以逗號隔開,且jsonPath和picPath的個數(shù)要一致。
* .單個例子:
* txtPath="/a/a.json"
* picTmpPath="/a/tmp/a.png"
* picPath="/a/a.png"
*
* .多個例子:
* txtPath="/a/a.txt,/b/b.json,/c/c.txt"
* picTmpPath="/a/tmp/a.png,/b/tmp/b.png,/c/tmp/c.png"
* picPath="/a/a.png,/b/b.png,/c/c.png"
*
* @author WuTingTing
* @param txtPath 圖表option信息的txt文件全路徑
* @param picTmpPath 圖片臨時存放路徑,生成后將轉(zhuǎn)移到picPath路徑下
* @param picPath 圖片文件全路徑
* @throws Exception
* @since JDK 1.8
*/
private static void doExecPhantomJS(String txtPath, String picTmpPath, String picPath, String width, String height)
throws BusinessException, Exception
{
String cmd = getCMD(txtPath, picTmpPath, picPath, width, height);
logger.info("圖片生成命令:" + cmd);
BufferedReader processInput = null;
// 檢查文件是否存在
boolean existP = FileUtils.fileExists(phantomPath);
boolean existJ = FileUtils.fileExists(JSpath);
if (!existP || !existJ)
{
throw new BusinessException()
.setMsg("生成圖片必要文件缺失:" + (!existP ? "phantomjs " : "") + (!existJ ? "echarts-convert.js、" : ""));
}
try
{
Process process = Runtime.getRuntime().exec(cmd);
processInput = new BufferedReader(
new InputStreamReader(process.getInputStream(), Charset.forName("UTF-8")));
String line = "";
while ((line = processInput.readLine()) != null)
{
// 執(zhí)行信息打印
logger.info(line);
}
int waitFor = process.waitFor();
if (waitFor != 0)
{
logger.info("圖片生成過程,非正常退出,請檢查是否存在異常");
}
}
finally
{
if (processInput != null)
{
try
{
processInput.close();
}
catch (IOException e)
{
logger.error("io close fail");
}
}
}
}
/**
* getCMD : (拼接命令). <br/>
*
* @author
* @param txtPath 圖表option信息的txt文件全路徑
* @param picTmpPath 圖片臨時存放路徑,生成后將轉(zhuǎn)移到picPath路徑下
* @param picPath 圖片文件全路徑
* @param width 自定義截圖寬度
* @param height 自定義截圖高度
* @return
* @throws BusinessException
* @since JDK 1.8
*/
private static String getCMD(String txtPath, String picTmpPath, String picPath, String width, String height)
throws BusinessException
{
if (StringUtils.isBlank(txtPath) || StringUtils.isBlank(picTmpPath) || StringUtils.isBlank(picPath))
{
logger.error("txtPath or picTmpPath or picPath is blank");
throw new BusinessException().setMsg("doExecPhantomJS 入?yún)㈠e誤");
}
if (StringUtils.isNotBlank(width) && StringUtils.isNotBlank(height))
{
return phantomPath + " " + JSpath + " -txtPath " + txtPath + " -picTmpPath " + picTmpPath + " -picPath "
+ picPath + " -width " + width + " -height " + height;
}
else
{
// 未自定義截圖寬度和高度,會使用默認寬、高
return phantomPath + " " + JSpath + " -txtPath " + txtPath + " -picTmpPath " + picTmpPath + " -picPath "
+ picPath;
}
}
public static void main(String[] args)
{
try
{
// 測試同時生成兩張圖
// 注意:要先手動創(chuàng)建兩個空文件G:\\test\\1.png和G:\\test\\2.png;要提前將echarts的option數(shù)據(jù)寫到G:\\test\\testOption.txt和G:\\test\\testOption2.txt中
doExecPhantomJS_deleteFileAfterException("G:\\test\\testOption.txt,G:\\test\\testOption2.txt", "G:\\test\\1.png,G:\\test\\2.png", "G:\\test\\111.png,G:\\test\\222.png");
}
catch (Exception e)
{
e.printStackTrace();
}
Paragraph ph1 = ReportFormUtil.createImageParagraphByPath("1、段落標題111", "G:\\test\\111.png", 35, 35,"這是一段前綴描述信息111", "這是一段后綴描述信息111");
Paragraph ph2 = ReportFormUtil.createImageParagraphByPath("2、段落標題222", "G:\\test\\222.png", 35, 35, "這是一段前綴描述信息222", "這是一段后綴描述信息222");
List<Paragraph> phs = new ArrayList<Paragraph>();
phs.add(ph1);
phs.add(ph2);
ReportFormUtil.createPDFDocumentToDisk("封面名稱", "小標題", "", phs, "G:\\test\\document.pdf");
}
}
注意:
我自己嘗試后發(fā)現(xiàn),phantomJS調(diào)用的js里面若用了一些較新的語法,phantomJS是不支持的,例如:const、let、=>、帶默認值的函數(shù)function test( a=1,b=[] ){} 、文章來源地址http://www.zghlxwxcb.cn/news/detail-698539.html
到了這里,關(guān)于【生成PDF】【JAVA】純后臺生成Echarts圖片,并將圖片生成到PDF文檔的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!