近期遇到一个需求:
公司的电子税票是以pdf文件格式保存的,这也是统一的标准格式。但是为了方便在手机App上查看,需要将税票转换为jpg格式。
解决:
拿到这个题目后,我第一反应就是去找一个Java的第三方库来实现转换。
尝试了不少的第三方库,比如pdfbox,ImageMagick等,但是效果都不理想。要么是转换出来的中文丢失,要么是表格数据排版混乱,有的甚至连图片都丢失。网上说jpedal效果不错,不过由于是商业版的无法尝试。
Java的搜索了一圈后发现都不行。想想要不看看C#有没有好的库呢。然后就搜索到了今天的解决方案所用的Adobe Acorbat的Acrobat.dll 来进行转换的办法。
Java无法直接使用dll,于是还用了jacob来调用dll 来实现。
不再罗嗦,直接上代码。
jacob项目地址 https://sourceforge.net/projects/jacob-project/
以下代码Pdf2Jpg2参考自 http://blog.csdn.net/love_5209/article/details/19162185
我主要做的修改是:将原来程序的每页pdf转换为一个jpg文件。修改为整个pdf所有页面拼接为一个jpg文件。这样方便存储。
主要思路是:先用一个循环获取到pdf文件的宽度w和高度h。然后设置一个宽度w,高度h的BufferedImage对象。然后在用一个循环读取每页的pdf内容,再粘贴到BufferedImage上,循环中适当调整粘贴的坐标。最后在循环外将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页数
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文件的页数
pageNum = Dispatch.call(pdfObject, "GetNumPages").toInt();
int allimgHeight = 0;
int allimgWidth = 0;
for (int i = 0; i < pageNum; i++) {
// 根据页码得到单页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 pageHight = new Stack();
int tmpHight = 0;
for (int i = 0; i < pageNum; i++) {
// 根据页码得到单页PDF
Dispatch page = Dispatch.call(pdfObject, "AcquirePage",
new Variant(i)).toDispatch();
// 得到PDF单页大小的Point对象
Dispatch pagePoint = Dispatch.call(page, "GetSize")
.toDispatch();
// 创建PDF位置对象,为拷贝图片到剪贴板做准备
ActiveXComponent pdfRect = new ActiveXComponent("AcroExch.Rect");
// 得到单页PDF的宽
//int imgWidth = (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times);
//使用最宽的页面作为统一宽度。
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剪切板,参数:位置对象,宽起点,高起点,分辨率
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 {
// 关闭PDF
app.invoke("Close", new Variant[] {});
ComThread.Release();//关闭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数据库。具体代码如下:
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位编码转成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编码内容转换为字节数组
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转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);
}
// 刷新此输出流并强制写出所有缓冲的输出字节
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) {
// 从数据库中读取图片。
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) {
// 把图片更新回数据库
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();
}
}
}
这个文件包含很多方法:
将数据库中base64编码的pdf编码读出保存为pdf文件 base64StringToPdf
将图片写入Oracle数据库 WirteImgToDB
从Oracle读出图片并保存为文件 ReadImgFromDB
最后通过一个AllInOne将这些方法串联起来。就可以达到读取数据库中的pdf base64编码,然后转换为pdf,再转换为jpg,最后将jpg的二进制写入数据库。
通过将这两个源代码打包为一个jar,再做一个定时任务,就可以自动去寻找没有转换生成jpg的税票,自动生成了。
需要注意的是:使用jacob.jar 需要找对版本,并将对应的平台(x86或者x64)的dll放到对应的java.library.path中。一般复制到java运行环境jre对应的bin目录下即可。
推荐使用jacob 1.18版本。因为1.17版本在Eclispe下面运行没有问题,但是在命令行里面运行的时候总是提示 NoSuchFieldError: m_pDispatch 一切都是对的找不到原因。最后果断换为1.18版本,就ok了。
jacob 1.18 需要Java7才能支持哦!