近期遇到一個需求:
公司的電子稅票是以pdf文件格式保存的,這也是統(tǒng)一的標準格式。但是為了方便在手機App上查看,需要將稅票轉(zhuǎn)換為jpg格式。
解決:
拿到這個題目后,我第一反應就是去找一個Java的第三方庫來實現(xiàn)轉(zhuǎn)換。
嘗試了不少的第三方庫,比如pdfbox,ImageMagick等,但是效果都不理想。要么是轉(zhuǎn)換出來的中文丟失,要么是表格數(shù)據(jù)排版混亂,有的甚至連圖片都丟失。網(wǎng)上說jpedal效果不錯,不過由于是商業(yè)版的無法嘗試。
Java的搜索了一圈后發(fā)現(xiàn)都不行。想想要不看看C#有沒有好的庫呢。然后就搜索到了今天的解決方案所用的Adobe Acorbat的Acrobat.dll 來進行轉(zhuǎn)換的辦法。
Java無法直接使用dll,于是還用了jacob來調(diào)用dll 來實現(xiàn)。
不再羅嗦,直接上代碼。
jacob項目地址 https://sourceforge.net/projects/jacob-project/
以下代碼Pdf2Jpg2參考自 http://blog.csdn.net/love_5209/article/details/19162185
我主要做的修改是:將原來程序的每頁pdf轉(zhuǎn)換為一個jpg文件。修改為整個pdf所有頁面拼接為一個jpg文件。這樣方便存儲。
主要思路是:先用一個循環(huán)獲取到pdf文件的寬度w和高度h。然后設置一個寬度w,高度h的BufferedImage對象。然后在用一個循環(huán)讀取每頁的pdf內(nèi)容,再粘貼到BufferedImage上,循環(huán)中適當調(diào)整粘貼的坐標。最后在循環(huán)外將BufferedImage對象輸出到jpg文件,就完成了。
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頁數(shù)
int pageNum = 0;
// PDF寬、高
int x, y = 0;
// PDF控制對象
Dispatch pdfObject = null;
// PDF坐標對象
Dispatch pointxy = null;
// pdfActiveX PDDoc對象 主要建立PDF對象
ActiveXComponent app = new ActiveXComponent("AcroExch.PDDoc");
// pdfActiveX PDF的坐標對象
ActiveXComponent point = new ActiveXComponent("AcroExch.Point");
try {
// 得到控制對象
pdfObject = app.getObject();
// 得到坐標對象
pointxy = point.getObject();
// 打開PDF文件,建立PDF操作的開始
Dispatch.call(pdfObject, "Open", new Variant(filepath));
// 得到當前打開PDF文件的頁數(shù)
pageNum = Dispatch.call(pdfObject, "GetNumPages").toInt();
int allimgHeight = 0;
int allimgWidth = 0;
for (int i = 0; i < pageNum; i++) {
// 根據(jù)頁碼得到單頁PDF
Dispatch page = Dispatch.call(pdfObject, "AcquirePage",
new Variant(i)).toDispatch();
// 得到PDF單頁大小的Point對象
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ù)頁碼得到單頁PDF
Dispatch page = Dispatch.call(pdfObject, "AcquirePage",
new Variant(i)).toDispatch();
// 得到PDF單頁大小的Point對象
Dispatch pagePoint = Dispatch.call(page, "GetSize")
.toDispatch();
// 創(chuàng)建PDF位置對象,為拷貝圖片到剪貼板做準備
ActiveXComponent pdfRect = new ActiveXComponent("AcroExch.Rect");
// 得到單頁PDF的寬
//int imgWidth = (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times);
//使用最寬的頁面作為統(tǒng)一寬度。
int imgWidth = allimgWidth;
// 得到單頁PDF的高
int imgHeight = (int) (Dispatch.get(pagePoint, "y").toInt() * 2 * times);
// 控制PDF位置對象
Dispatch pdfRectDoc = pdfRect.getObject();
// 設置PDF位置對象的值
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));
// 將設置好位置的PDF拷貝到Windows剪切板,參數(shù):位置對象,寬起點,高起點,分辨率
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進程
}
}
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);
}
}
我在得到jpg文件后,還將jpg文件用二進制的形式回寫到了Oracle數(shù)據(jù)庫。具體代碼如下:
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);
}
// 刷新此輸出流并強制寫出所有緩沖的輸出字節(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ù)庫中讀取圖片。
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) {
// 把圖片更新回數(shù)據(jù)庫
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();
}
}
}
這個文件包含很多方法:
將數(shù)據(jù)庫中base64編碼的pdf編碼讀出保存為pdf文件 base64StringToPdf
將圖片寫入Oracle數(shù)據(jù)庫 WirteImgToDB
從Oracle讀出圖片并保存為文件 ReadImgFromDB
最后通過一個AllInOne將這些方法串聯(lián)起來。就可以達到讀取數(shù)據(jù)庫中的pdf base64編碼,然后轉(zhuǎn)換為pdf,再轉(zhuǎn)換為jpg,最后將jpg的二進制寫入數(shù)據(jù)庫。
通過將這兩個源代碼打包為一個jar,再做一個定時任務,就可以自動去尋找沒有轉(zhuǎn)換生成jpg的稅票,自動生成了。
需要注意的是:使用jacob.jar 需要找對版本,并將對應的平臺(x86或者x64)的dll放到對應的java.library.path中。一般復制到java運行環(huán)境jre對應的bin目錄下即可。
推薦使用jacob 1.18版本。因為1.17版本在Eclispe下面運行沒有問題,但是在命令行里面運行的時候總是提示 NoSuchFieldError: m_pDispatch 一切都是對的找不到原因。最后果斷換為1.18版本,就ok了。
jacob 1.18 需要Java7才能支持哦!