近期遇到一個(gè)需求:
公司的電子稅票是以pdf文件格式保存的舆绎,這也是統(tǒng)一的標(biāo)準(zhǔn)格式哟沫。但是為了方便在手機(jī)App上查看,需要將稅票轉(zhuǎn)換為jpg格式。
解決:
拿到這個(gè)題目后压彭,我第一反應(yīng)就是去找一個(gè)Java的第三方庫(kù)來(lái)實(shí)現(xiàn)轉(zhuǎn)換。
嘗試了不少的第三方庫(kù)辱匿,比如pdfbox灶泵,ImageMagick等,但是效果都不理想补箍。要么是轉(zhuǎn)換出來(lái)的中文丟失改执,要么是表格數(shù)據(jù)排版混亂,有的甚至連圖片都丟失坑雅。網(wǎng)上說(shuō)jpedal效果不錯(cuò)辈挂,不過(guò)由于是商業(yè)版的無(wú)法嘗試。
Java的搜索了一圈后發(fā)現(xiàn)都不行裹粤。想想要不看看C#有沒(méi)有好的庫(kù)呢终蒂。然后就搜索到了今天的解決方案所用的Adobe Acorbat的Acrobat.dll 來(lái)進(jìn)行轉(zhuǎn)換的辦法。
Java無(wú)法直接使用dll遥诉,于是還用了jacob來(lái)調(diào)用dll 來(lái)實(shí)現(xiàn)拇泣。
不再羅嗦,直接上代碼突那。
jacob項(xiàng)目地址 https://sourceforge.net/projects/jacob-project/
以下代碼Pdf2Jpg2參考自 http://blog.csdn.net/love_5209/article/details/19162185
我主要做的修改是:將原來(lái)程序的每頁(yè)pdf轉(zhuǎn)換為一個(gè)jpg文件挫酿。修改為整個(gè)pdf所有頁(yè)面拼接為一個(gè)jpg文件。這樣方便存儲(chǔ)愕难。
主要思路是:先用一個(gè)循環(huán)獲取到pdf文件的寬度w和高度h早龟。然后設(shè)置一個(gè)寬度w,高度h的BufferedImage對(duì)象葱弟。然后在用一個(gè)循環(huán)讀取每頁(yè)的pdf內(nèi)容芝加,再粘貼到BufferedImage上藏杖,循環(huán)中適當(dāng)調(diào)整粘貼的坐標(biāo)。最后在循環(huán)外將BufferedImage對(duì)象輸出到j(luò)pg文件来吩,就完成了弟疆。
package com.invoicetopdf;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Stack;
import javax.imageio.ImageIO;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
public class Pdf2Jpg2 {
/**
*
* @param filepath
* pdf路徑
* @param savePath
* img保存路徑
* @param times
* 縮放比例 如:1f為原圖比例
* @throws IOException
*/
public static void savaPageAsJpgByAcrobat(String filepath, String savePath,
float times) throws IOException {
ComThread.InitSTA();//初始化com的線程
// 輸出
FileOutputStream out = null;
// PDF頁(yè)數(shù)
int pageNum = 0;
// PDF寬、高
int x, y = 0;
// PDF控制對(duì)象
Dispatch pdfObject = null;
// PDF坐標(biāo)對(duì)象
Dispatch pointxy = null;
// pdfActiveX PDDoc對(duì)象 主要建立PDF對(duì)象
ActiveXComponent app = new ActiveXComponent("AcroExch.PDDoc");
// pdfActiveX PDF的坐標(biāo)對(duì)象
ActiveXComponent point = new ActiveXComponent("AcroExch.Point");
try {
// 得到控制對(duì)象
pdfObject = app.getObject();
// 得到坐標(biāo)對(duì)象
pointxy = point.getObject();
// 打開(kāi)PDF文件恤溶,建立PDF操作的開(kāi)始
Dispatch.call(pdfObject, "Open", new Variant(filepath));
// 得到當(dāng)前打開(kāi)PDF文件的頁(yè)數(shù)
pageNum = Dispatch.call(pdfObject, "GetNumPages").toInt();
int allimgHeight = 0;
int allimgWidth = 0;
for (int i = 0; i < pageNum; i++) {
// 根據(jù)頁(yè)碼得到單頁(yè)P(yáng)DF
Dispatch page = Dispatch.call(pdfObject, "AcquirePage",
new Variant(i)).toDispatch();
// 得到PDF單頁(yè)大小的Point對(duì)象
Dispatch pagePoint = Dispatch.call(page, "GetSize")
.toDispatch();
if (allimgWidth < (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times)){
allimgWidth = (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times);
}
allimgHeight += (int) (Dispatch.get(pagePoint, "y").toInt() * 2 * times);
}
BufferedImage tag = new BufferedImage(allimgWidth, allimgHeight, 8);
Graphics graphics = tag.getGraphics();
Stack<Integer> pageHight = new Stack<Integer>();
int tmpHight = 0;
for (int i = 0; i < pageNum; i++) {
// 根據(jù)頁(yè)碼得到單頁(yè)P(yáng)DF
Dispatch page = Dispatch.call(pdfObject, "AcquirePage",
new Variant(i)).toDispatch();
// 得到PDF單頁(yè)大小的Point對(duì)象
Dispatch pagePoint = Dispatch.call(page, "GetSize")
.toDispatch();
// 創(chuàng)建PDF位置對(duì)象鸠天,為拷貝圖片到剪貼板做準(zhǔn)備
ActiveXComponent pdfRect = new ActiveXComponent("AcroExch.Rect");
// 得到單頁(yè)P(yáng)DF的寬
//int imgWidth = (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times);
//使用最寬的頁(yè)面作為統(tǒng)一寬度稠集。
int imgWidth = allimgWidth;
// 得到單頁(yè)P(yáng)DF的高
int imgHeight = (int) (Dispatch.get(pagePoint, "y").toInt() * 2 * times);
// 控制PDF位置對(duì)象
Dispatch pdfRectDoc = pdfRect.getObject();
// 設(shè)置PDF位置對(duì)象的值
Dispatch.put(pdfRectDoc, "Left", new Integer(0));
Dispatch.put(pdfRectDoc, "Right", new Integer(imgWidth));
Dispatch.put(pdfRectDoc, "Top", new Integer(0));
Dispatch.put(pdfRectDoc, "Bottom", new Integer(imgHeight));
// 將設(shè)置好位置的PDF拷貝到Windows剪切板剥纷,參數(shù):位置對(duì)象,寬起點(diǎn)晦鞋,高起點(diǎn)线定,分辨率
Dispatch.call(page, "CopyToClipboard", new Object[] {
pdfRectDoc, 0, 0, 200 * times });
Image image = getImageFromClipboard();
if (i==0){
graphics.drawImage(image, 0, 0, null);
}
else {
graphics.drawImage(image, 0, tmpHight, null);
}
tmpHight += imgHeight;
//graphics.dispose();
// 輸出圖片
//ImageIO.write(tag, "JPEG", new File(savePath + (i+1) + ".jpg"));
}
graphics.dispose();
ImageIO.write(tag, "JPEG", new File(savePath + "all.jpg"));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關(guān)閉PDF
app.invoke("Close", new Variant[] {});
ComThread.Release();//關(guān)閉com的線程 真正kill進(jìn)程
}
}
public static Image getImageFromClipboard() throws Exception {
Clipboard sysc = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable cc = sysc.getContents(null);
if (cc == null)
return null;
else if (cc.isDataFlavorSupported(DataFlavor.imageFlavor))
return (Image) cc.getTransferData(DataFlavor.imageFlavor);
return null;
}
public static void main(String[] args) throws IOException {
//System.setProperty("java.library.path","d:/test/");
savaPageAsJpgByAcrobat("d:/test/11.pdf",
"d:/test/", 1f);
}
}
我在得到j(luò)pg文件后湾趾,還將jpg文件用二進(jìn)制的形式回寫(xiě)到了Oracle數(shù)據(jù)庫(kù)铛楣。具體代碼如下:
package com.invoicetopdf;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;
public class AutoInvoiceToPDF {
// base64位編碼轉(zhuǎn)成PDF
public static void base64StringToPdf(String base64Content, String filePath)
throws IOException {
BASE64Decoder decoder = new BASE64Decoder();
BufferedInputStream bis = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
byte[] bytes = decoder.decodeBuffer(base64Content);// base64編碼內(nèi)容轉(zhuǎn)換為字節(jié)數(shù)組
ByteArrayInputStream byteInputStream = new ByteArrayInputStream(
bytes);
bis = new BufferedInputStream(byteInputStream);
File file = new File(filePath);
File path = file.getParentFile();
if (!path.exists()) {
path.mkdirs();
}
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[1024];
int length = bis.read(buffer);
while (length != -1) {
bos.write(buffer, 0, length);
length = bis.read(buffer);
}
bos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// closeStream(bis, fos, bos);
bis.close();
fos.close();
bos.close();
// System.out.println("生成成功");
}
}
// pdf轉(zhuǎn)BASE64位編碼
public static String PDFToBase64(File file) {
BASE64Encoder encoder = new BASE64Encoder();
FileInputStream fin = null;
BufferedInputStream bin = null;
ByteArrayOutputStream baos = null;
BufferedOutputStream bout = null;
try {
fin = new FileInputStream(file);
bin = new BufferedInputStream(fin);
baos = new ByteArrayOutputStream();
bout = new BufferedOutputStream(baos);
byte[] buffer = new byte[1024];
int len = bin.read(buffer);
while (len != -1) {
bout.write(buffer, 0, len);
len = bin.read(buffer);
}
// 刷新此輸出流并強(qiáng)制寫(xiě)出所有緩沖的輸出字節(jié)
bout.flush();
byte[] bytes = baos.toByteArray();
return encoder.encodeBuffer(bytes).trim();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fin.close();
bin.close();
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
// GZIP壓縮
public static String gzip(String primStr) {
if (primStr == null || primStr.length() == 0) {
return primStr;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = null;
try {
gzip = new GZIPOutputStream(out);
gzip.write(primStr.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (gzip != null) {
try {
gzip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new sun.misc.BASE64Encoder().encode(out.toByteArray());
}
// GZIP解壓
public static String gunzip(String compressedStr) {
if (compressedStr == null) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = null;
GZIPInputStream ginzip = null;
byte[] compressed = null;
String decompressed = null;
try {
compressed = new sun.misc.BASE64Decoder()
.decodeBuffer(compressedStr);
in = new ByteArrayInputStream(compressed);
ginzip = new GZIPInputStream(in);
byte[] buffer = new byte[1024];
int offset = -1;
while ((offset = ginzip.read(buffer)) != -1) {
out.write(buffer, 0, offset);
}
decompressed = out.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ginzip != null) {
try {
ginzip.close();
} catch (IOException e) {
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
}
}
}
return decompressed;
}
public static void ReadImgFromDB(Connection conn, int fphm) {
// 從數(shù)據(jù)庫(kù)中讀取圖片。
String sql = "select jpgbm from pdfinfo where fphm = '39410355'";
Statement stmt;
FileOutputStream fout;
ResultSet rs;
try {
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
while (rs.next()) {
Blob blob = rs.getBlob(1);
byte barr[] = blob.getBytes(1, (int) blob.length());
// System.out.println("blob length:" +blob.length());
fout = new FileOutputStream("d:/dbjpg.jpg");
fout.write(barr);
fout.flush();
fout.close();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
public static void WirteImgToDB(Connection conn, int fphm) {
// 把圖片更新回?cái)?shù)據(jù)庫(kù)
PreparedStatement ps;
try {
ps = conn
.prepareStatement("update pdfinfo set jpgbm = ? where fphm = ? ");
FileInputStream fin;
fin = new FileInputStream("d:\\test\\all.jpg");
// System.out.println("file size:" + fin.available());
ps.setBinaryStream(1, fin, fin.available());
ps.setInt(2, fphm);
int i = ps.executeUpdate();
ps.close();
// System.out.println(i + " records affected");
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void AllInOne(Connection conn, int fphm) {
String pdfbm = "";
try {
Statement stmt = conn.createStatement();
String sql = "select pdfbm from pdfinfo where fphm = '" + fphm
+ "'";
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
pdfbm = rs.getString("pdfbm");
}
rs.close();
stmt.close();
pdfbm = gunzip(pdfbm);
// 生成pdf
base64StringToPdf(pdfbm, "d:\\test\\11.pdf");
Pdf2Jpg2.savaPageAsJpgByAcrobat("d:\\test\\11.pdf", "d:\\test\\",
0.9f);
WirteImgToDB(conn, fphm);
System.out.println("fphm:" + fphm + " done!");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String url = "jdbc:oracle:thin:@172.xx.x.xx:1521:xxxx";
String user = "xxxxx";
String password = "xxxxx";
Connection conn = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
e.printStackTrace();
}
int fphm = 0;
String sqlString = " select fphm from pdfinfo where jpgbm is null and rownum<5000 order by indate desc ";
Statement stmt;
try {
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sqlString);
while (rs.next()) {
fphm = rs.getInt("fphm");
AllInOne(conn, fphm);
}
rs.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
這個(gè)文件包含很多方法:
將數(shù)據(jù)庫(kù)中base64編碼的pdf編碼讀出保存為pdf文件 base64StringToPdf
將圖片寫(xiě)入Oracle數(shù)據(jù)庫(kù) WirteImgToDB
從Oracle讀出圖片并保存為文件 ReadImgFromDB
最后通過(guò)一個(gè)AllInOne將這些方法串聯(lián)起來(lái)。就可以達(dá)到讀取數(shù)據(jù)庫(kù)中的pdf base64編碼助琐,然后轉(zhuǎn)換為pdf祭埂,再轉(zhuǎn)換為jpg,最后將jpg的二進(jìn)制寫(xiě)入數(shù)據(jù)庫(kù)兵钮。
通過(guò)將這兩個(gè)源代碼打包為一個(gè)jar,再做一個(gè)定時(shí)任務(wù)泰演,就可以自動(dòng)去尋找沒(méi)有轉(zhuǎn)換生成jpg的稅票睦焕,自動(dòng)生成了。
需要注意的是:使用jacob.jar 需要找對(duì)版本袜炕,并將對(duì)應(yīng)的平臺(tái)(x86或者x64)的dll放到對(duì)應(yīng)的java.library.path中偎窘。一般復(fù)制到j(luò)ava運(yùn)行環(huán)境jre對(duì)應(yīng)的bin目錄下即可乌助。
推薦使用jacob 1.18版本。因?yàn)?.17版本在Eclispe下面運(yùn)行沒(méi)有問(wèn)題评架,但是在命令行里面運(yùn)行的時(shí)候總是提示 NoSuchFieldError: m_pDispatch 一切都是對(duì)的找不到原因眷茁。最后果斷換為1.18版本,就ok了纵诞。
jacob 1.18 需要Java7才能支持哦上祈!