前几篇文章我们分别介绍了显示文件列表、解析pdf、手写画板及画笔设置的功能了,今天我们就介绍一下,最后最关键的一部分-手写签名效果。先看看效果图:

                选定位置                                    画板上写字                                       预览签名效果


Java 对接E签宝接口 e签宝打不开_Image

          

Java 对接E签宝接口 e签宝打不开_屏幕坐标_02

           

Java 对接E签宝接口 e签宝打不开_屏幕坐标_03

一、实现原理:

对于pdf文件上进行相关操作,本人并没找到一些比较好的方法,为了实现签名效果,尝试了很多方法也没达到预期效果,最后这种实现方法相对好些,也比较简单。其基本思想是根据对pdf文件加印章及水印来实现的,事先我们准备一张透明的png图片,当做手写画板的背景图片,写字时实际就在这张图片操作了,最后将手写的画板内容重新保存一种新的png背景透明图片,就是对pdf文件的操作了,对pdf操作要用到第三方jar包droidText0.5.jar包,通过里面的方法Image img = Image.getInstance(InPicFilePath);完成将透明图片加入到pdf文件上,最后重新生成新的pdf文件,签名就完成了。并不是直接对pdf文件进行操作,不知道其他的实现方法有哪些,也请告知一下。下面我就把自己实现具体过程介绍一下:

新建一个类,用于处理签名pdf文件HandWriteToPDF.java:

1. package com.xinhui.handwrite;  
2.   
3. import java.io.FileOutputStream;  
4. import java.io.InputStream;  
5. import java.security.PrivateKey;  
6. import java.security.PublicKey;  
7.   
8. import android.util.Log;  
9.   
10. import com.artifex.mupdf.MuPDFActivity;  
11. import com.artifex.mupdf.MuPDFPageView;  
12. import com.lowagie.text.Image;  
13. import com.lowagie.text.pdf.PdfArray;  
14. import com.lowagie.text.pdf.PdfContentByte;  
15. import com.lowagie.text.pdf.PdfDictionary;  
16. import com.lowagie.text.pdf.PdfName;  
17. import com.lowagie.text.pdf.PdfObject;  
18. import com.lowagie.text.pdf.PdfReader;  
19. import com.lowagie.text.pdf.PdfStamper;  
20. import com.xinhui.electronicsignature.R;  
21.   
22.   
23.   
24. public class HandWriteToPDF {  
25. private String InPdfFilePath;  
26. private String outPdfFilePath;  
27. private String InPicFilePath;  
28. public static int writePageNumble;//要签名的页码  
29.     HandWriteToPDF(){  
30.           
31.     }  
32. public HandWriteToPDF(String InPdfFilePath,String outPdfFilePath,String InPicFilePath){  
33. this.InPdfFilePath = InPdfFilePath;  
34. this.outPdfFilePath = outPdfFilePath;  
35. this.InPicFilePath = InPicFilePath;  
36.     }  
37. public String getInPdfFilePath() {  
38. return InPdfFilePath;  
39.     }  
40. public void setInPdfFilePath(String inPdfFilePath) {  
41.         InPdfFilePath = inPdfFilePath;  
42.     }  
43. public String getOutPdfFilePath() {  
44. return outPdfFilePath;  
45.     }  
46. public void setOutPdfFilePath(String outPdfFilePath) {  
47. this.outPdfFilePath = outPdfFilePath;  
48.     }  
49. public String getInPicFilePath() {  
50. return InPicFilePath;  
51.     }  
52. public void setInPicFilePath(String inPicFilePath) {  
53.         InPicFilePath = inPicFilePath;  
54.     }  
55.       
56. public void addText(){  
57. try{  
58. new PdfReader(InPdfFilePath, "PDF".getBytes());//选择需要印章的pdf  
59. new FileOutputStream(outPdfFilePath);  
60.             PdfStamper stamp;  
61. new PdfStamper(reader, outStream);//加完印章后的pdf  
62. //设置在第几页打印印章  
63. //用pdfreader获得当前页字典对象.包含了该页的一些数据.比如该页的坐标轴信  
64.             PdfDictionary p = reader.getPageN(writePageNumble);  
65. //拿到mediaBox 里面放着该页pdf的大小信息.    
66. new PdfName("MediaBox"));   
67. //po是一个数组对象.里面包含了该页pdf的坐标轴范围.    
68.             PdfArray pa = (PdfArray) po;    
69. //选择图片  
70. 1);  
71. 300,150);//控制图片大小,原始比例720:360  
72. //调用书写pdf位置方法  
73. 1).floatValue());  
74.             over.addImage(img);           
75.             stamp.close();  
76. catch (Exception e) {  
77. // TODO Auto-generated catch block  
78.             e.printStackTrace();  
79.         }  
80.     }  
81. /**
82.      * 功能:处理要书写pdf位置
83.      * @param img
84.      */  
85. private void writingPosition(Image img ,float pdfHigth){  
86.           
87. int pdfSizeX = MuPDFPageView.pdfSizeX;  
88. int pdfSizeY = MuPDFPageView.pdfSizeY;  
89. int pdfPatchX = MuPDFPageView.pdfPatchX;  
90. int pdfPatchY = MuPDFPageView.pdfPatchY;  
91. int pdfPatchWidth = MuPDFPageView.pdfPatchWidth;  
92. int pdfPatchHeight = MuPDFPageView.pdfPatchHeight;  
93. int y = MuPDFActivity.y+180;  
94. float n = pdfPatchWidth*1.0f;  
95. float m = pdfPatchHeight*1.0f;  
96.         n = pdfSizeX/n;  
97.         m = pdfSizeY/m;  
98. if(n == 1.0f){  
99. //pdf页面没有放大时的比例  
100. if(MuPDFActivity.y >= 900){  
101. 5/6,0);  
102. else if(MuPDFActivity.y <= 60){  
103. 5/6,pdfHigth-150);  
104. else{  
105. 5/6,pdfHigth-((MuPDFActivity.y+120)*5/6));  
106.             }  
107. else{  
108. //pdf页面放大时的比例,这里坐标不精确???  
109.             n = (MuPDFActivity.x+pdfPatchX)/n;  
110.             m = (MuPDFActivity.y+pdfPatchY)/m;  
111. 5/6,pdfHigth-((m+120)*5/6));  
112.         }  
113.     }  
114. }

其中:

1. img.setAbsolutePosition(MuPDFActivity.x*5/6,pdfHigth-((MuPDFActivity.y+120)*5/6));

是用来控制要在pdf文件上的签名位置,这个得说说,我并没有搞明白怎么设置这个坐标值才是最合适的,屏幕和pdf文件的坐标原点都是在左上角,setAbsolutPosition(float x,float y)的坐标原点是在屏幕的左下角,还有就是pdf实际分辨率和手机的实际分辨率又不是相同的,在我移动坐标,通过获取的手机屏幕的坐标:

1. /**
2.      * 功能:自定义一个显示截屏区域视图方法
3.      * */  
4. public void screenShot(MotionEvent e2){  
5. //这里实现截屏区域控制  
6. /*if(MuPDFActivity.screenShotView == null || !(MuPDFActivity.screenShotView.isShown())){
7.             MuPDFActivity.screenShotView = new MyView(MuPDFActivity.THIS);
8.             MuPDFActivity.THIS.addContentView(MuPDFActivity.screenShotView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
9.         }*/  
10. int) e2.getX();  
11. int) e2.getY();  
12. new View(MuPDFActivity.THIS);  
13.         screenView = MuPDFActivity.THIS.getWindow().getDecorView();  
14. true);  
15.         screenView.buildDrawingCache();  
16.         Bitmap screenbitmap = screenView.getDrawingCache();  
17.         screenWidth = screenbitmap.getWidth();  
18.         screenHeight = screenbitmap.getHeight();  
19. int oX = MuPDFActivity.oX;  
20. int oY = MuPDFActivity.oY;  
21. int x = 0  ;  
22. int y = 0 ;  
23. int m = 0 ;  
24. int n = 0 ;  
25. //oX = (int) event.getX();  
26. //oY = (int) event.getY();  
27. if(oX -180 <= 0){  
28. if(oY - 90 <= 0){  
29. //左边界和上边界同时出界  
30. 0;  
31. 0;  
32. 360;  
33. 180;  
34. else if(oY + 90 >= screenHeight){  
35. //左边界和下边界同时出界  
36. 0;  
37. 180;  
38. 360;  
39.                 n = screenHeight;  
40. else{  
41. //只有左边界  
42. 0;  
43. 90;  
44. 360;  
45. 180;  
46.             }  
47. else if(oX + 180 >= screenWidth){  
48. if(oY - 90 <= 0){  
49. //右边界和上边界同时出界  
50. 360;  
51. 0;  
52.                 m = screenWidth;  
53. 180;  
54. else if(oY + 90 >= screenHeight){  
55. //右边界和下边界同时出界  
56.                   
57.                   
58. else{  
59. //只有右边界出界  
60. 360;  
61. 90;  
62.                 m = screenWidth;  
63. 180;  
64.             }  
65. else if(oY - 90 <= 0){  
66. //只有上边界出界  
67. 90;  
68. 0;  
69. 360;  
70. 180;  
71. else if(oY + 90 >= screenHeight){  
72. //只有下边界出界  
73. 180;  
74. 180;  
75. 360;  
76. 180;  
77. else{  
78. //都不出界  
79. 180;  
80. 90;  
81. 360;  
82. 180;  
83.         }  
84. //根据屏幕坐标,显示要截图的区域范围  
85.         MuPDFActivity.x = x;  
86.         MuPDFActivity.y = y;  
87.         MuPDFActivity.screenShotView.setSeat(x, y, m, n);  
88.         MuPDFActivity.screenShotView.postInvalidate();  
89. }

在类ReaderView.java中实现动态获取坐标,本人并没有搞明白屏幕坐标和pdf文件坐标已经setAbsolutPosition()方法三者之间的关系,通过很多数据测试得出了在没有放大pdf页面的情况下关系如下:

1. img.setAbsolutePosition(MuPDFActivity.x*5/6,pdfHigth-((MuPDFActivity.y+120)*5/6));

也就是屏幕坐标和setAbsolutPosition()坐标是5:6的关系,这样对于没有放大页面的情况下,坐标对应是非常准确的,但是当方法pdf页面再去取坐标时,就混乱了,这个问题目前还没有解决,知道好的方法的朋友一定记得告诉在下呀!

定义了三个用于保存文件路径的变量:

1. private String InPdfFilePath;  
2. private String outPdfFilePath;  
3. private String InPicFilePath;

类建好后,就是在MuPdfActivity类中对相关按钮功能进行完善了:

1. @Override  
2. public void onClick(View v) {  
3. // TODO Auto-generated method stub  
4. switch (v.getId()) {  
5. case R.id.cancel_bt://撤销已签名pdf文件  
6. if(isPreviewPDF){  
7. new Builder(this);  
8. "提醒:撤销后,已签名文件文件将无法恢复,是否继续?")  
9. "继续", new DialogInterface.OnClickListener() {  
10.                       
11. @Override  
12. public void onClick(DialogInterface arg0, int arg1) {  
13. // TODO Auto-generated method stub  
14. try{  
15.                             core = openFile(InPdfFilePath);  
16. new File(OutPdfFilePath);  
17.                             file.delete();  
18. catch (Exception e) {  
19. // TODO: handle exception  
20. this, "无法打开该文件", Toast.LENGTH_SHORT).show();  
21.                         }  
22.                         createUI(InstanceState);  
23. false;//重新解析pdf,恢复初始值  
24. true;//重新释放对pdf手势操作  
25. false;//重新解析pdf,恢复初始值  
26. false;//  
27. false;  
28.                     }  
29.                 })  
30. "取消", null)  
31.                 .create()  
32.                 .show();  
33. else{  
34. this, "没有要撤销的签名文件", Toast.LENGTH_SHORT).show();  
35.             }  
36. break;  
37. case R.id.clear_bt://清除画板字迹  
38. if(mAddPicButton.getContentDescription().equals("取消签名")){  
39.                 handWritingView.clear();  
40. else{  
41. this, "手写板未打开", Toast.LENGTH_SHORT).show();  
42.             }  
43. break;  
44. case R.id.add_pic_bt://打开手写画板  
45. //记录当前签名页码  
46.             writingPageNumble = mDocView.getDisplayedViewIndex();  
47. if(mAddPicButton.getContentDescription().equals("开始签名")){  
48. if(screenShotView.isShown()){  
49.                     screenShotView.setVisibility(View.INVISIBLE);  
50.                     handWritingView.setVisibility(View.VISIBLE);  
51.                       
52. "取消签名");  
53. "锁定屏幕");  
54. true;  
55. else if(isPreviewPDF){  
56. this, "预览模式", Toast.LENGTH_SHORT).show();  
57. else{  
58. this, "请先选定书写区域", Toast.LENGTH_SHORT).show();  
59.                 }  
60. else{  
61.                 handWritingView.setVisibility(View.GONE);  
62. "开始签名");  
63. false;  
64. true;//释放pdf手势操作  
65.             }  
66. break;  
67. case R.id.screenshot_ib://打开区域选择view  
68. if(screenShotView == null){  
69. new ScreenShotView(this);  
70.             }  
71. if(isPreviewPDF){  
72. this, "预览模式", Toast.LENGTH_SHORT).show();  
73. else if(!isPreviewPDF && isWriting){  
74. this, "正在签名……", Toast.LENGTH_SHORT).show();  
75. else{  
76. if(!screenShotView.isShown() && !isScreenShotViewShow){  
77. this.addContentView(screenShotView,   
78. new LayoutParams(LayoutParams.WRAP_CONTENT,   
79.                                     LayoutParams.WRAP_CONTENT));  
80.                       
81.                       
82. 360, y+180);  
83.                     screenShotView.postInvalidate();  
84. true;  
85.                 }  
86. if(mScreenShot.getContentDescription().equals("锁定屏幕")){  
87. false;  
88. "释放屏幕");  
89.                     screenShotView.setVisibility(View.VISIBLE);  
90. else{  
91. true;  
92. "锁定屏幕");  
93.                     screenShotView.setVisibility(View.INVISIBLE);  
94.                 }  
95.             }  
96. break;  
97. case R.id.confirm_bt://保存签名文件  
98. if(mAddPicButton.getContentDescription().equals("取消签名")){  
99. new saveImageAsyncTask(this);  
100.                 asyncTask.execute();  
101. true;  
102.                 handWritingView.setVisibility(View.INVISIBLE);  
103. "开始签名");  
104. true;  
105. false;  
106. else{  
107. this, "没有要保存的签名文件", Toast.LENGTH_SHORT).show();  
108.             }  
109. break;  
110. default:  
111. break;  
112. }

在保存文件时,对于程序来说,是比较耗时了,这样我们就用到了异步任务来完成耗时的操作:

由于这一排操作按钮

1. /** 
2.           * 运行在UI线程中,在调用doInBackground()之前执行 
3.           */   
4. @Override  
5. protected void onPreExecute() {  
6. // TODO Auto-generated method stub  
7. "正在处理……",Toast.LENGTH_SHORT).show();    
8.         }  
9.            
10. /** 
11.           *后台运行的方法,可以运行非UI线程,可以执行耗时的方法 
12.           */    
13. @Override  
14. protected Integer doInBackground(Void... arg0) {  
15. // TODO Auto-generated method stub  
16.             mSaveImage();  
17. return null;  
18.         }  
19.           
20. /**
21.          * 运行在ui线程中,在doInBackground()执行完毕后执行 
22.          */  
23. @Override  
24. protected void onPostExecute(Integer result) {  
25. // TODO Auto-generated method stub  
26. //super.onPostExecute(result);  
27.             createUI(InstanceState);  
28. "签名完成",Toast.LENGTH_SHORT).show();  
29.         }  
30. /**
31.          * 在publishProgress()被调用以后执行,publishProgress()用于更新进度 
32.          */  
33. @Override  
34. protected void onProgressUpdate(Integer... values) {  
35. // TODO Auto-generated method stub  
36. super.onProgressUpdate(values);  
37.         }  
38.     }  
39. /**
40.      * 功能:处理书写完毕的画板,重新生成bitmap
41.      */  
42. public void mSaveImage(){  
43.         HandWritingView.saveImage = Bitmap.createBitmap(handWritingView.HandWriting(HandWritingView.new1Bitmap));  
44.         HandWritingView mv = handWritingView;  
45.         storeInSDBitmap = mv.saveImage();  
46. new Canvas(storeInSDBitmap);  
47. new Paint();  
48. 0, 0, 0, 0);  
49.         canvas.isOpaque();  
50. 255);//设置签名水印透明度  
51. //这个方法  第一个参数是图片原来的大小,第二个参数是 绘画该图片需显示多少。  
52. //也就是说你想绘画该图片的某一些地方,而不是全部图片,  
53. //第三个参数表示该图片绘画的位置.  
54. 0, 0, paint);  
55. //保存签名过的pdf文件  
56.         previewPDFShow();  
57.     }  
58.       
59. /**
60.      * 功能:预览签名过的pdf
61.      */  
62. public  void previewPDFShow(){  
63.         String openNewPath = OutPdfFilePath;  
64.           
65. try{  
66. //打开已经签名好的文件进行预览  
67. //截屏坐标恢复默认  
68. 200;  
69. 200;  
70. catch (Exception e) {  
71. // TODO: handle exception  
72. "info", "------打开失败");  
73.         }  
74.     }  
75.       
76. /**
77.      * 功能:将签好名的bitmap保存到sd卡
78.      * @param bitmap
79.      */  
80. public static void storeInSD(Bitmap bitmap) {  
81. new File("/sdcard/签名");//要保存的文件地址和文件名  
82. if (!file.exists()) {  
83.             file.mkdir();  
84.         }  
85. new File(file, "签名" + ".png");  
86. try {  
87.             imageFile.createNewFile();  
88. new FileOutputStream(imageFile);  
89. 100, fos);  
90.             fos.flush();  
91.             fos.close();  
92.             addTextToPdf();  
93. catch (FileNotFoundException e) {  
94. // TODO Auto-generated catch block  
95.             e.printStackTrace();  
96. catch (IOException e) {  
97. // TODO Auto-generated catch block  
98.             e.printStackTrace();  
99.         }  
100.     }  
101.       
102. public static void addTextToPdf(){  
103.         String  SDCardRoot = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator;  
104. new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");         
105. new Date(System.currentTimeMillis());//获取当前时间         
106.         String currentSystemTime = formatter.format(curDate);      
107.         InPdfFilePath = MuPDFActivity.PATH;  
108. "/签名/已签名文件"+currentSystemTime+".pdf";  
109. "/签名/签名.png";  
110. new HandWriteToPDF(InPdfFilePath, OutPdfFilePath, InPicFilePath);  
111.         handWriteToPDF.addText();  
112.     }

Java 对接E签宝接口 e签宝打不开_屏幕坐标_04

之间存在逻辑关系:比如没有确定签名位置不能打开画板等,我们就需要判断什么时候应该打开,什么打不开并提示,这样我们就定义几个布尔类型的变量:

1. /**
2.      * 判断是否为预览pdf模式
3.      */  
4. public static boolean isPreviewPDF = false;  
5. /**
6.      * 判断是否正在书写
7.      */  
8. public static boolean isWriting = false;  
9. /**
10.      * 判断页面按钮是否显示
11.      */  
12. private boolean showButtonsDisabled;  
13. /**
14.      * 判断截屏视图框是否显示
15.      */  
16. private static boolean isScreenShotViewShow = false;

1. /**
2.      * NoTouch =false 屏蔽pdf手势操作,为true时释放pdf手势操作
3.      */  
4. public static boolean NoTouch = true;




为了在我们进行选择位置和签名时pdf不会再监听手势操作,我们要做相应的屏蔽:

1. public boolean onScale(ScaleGestureDetector detector) {  
2. //截屏视图不显示时,手势操作可以进行  
3. if(NoTouch){  
4. float previousScale = mScale;  
5.             mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE);  
6. //缩放比例  
7. //Log.e("info", "--->scalingFactor="+scalingFactor);  
8.             View v = mChildViews.get(mCurrent);  
9. if (v != null) {  
10. // Work out the focus point relative to the view top left  
11. int viewFocusX = (int)detector.getFocusX() - (v.getLeft() + mXScroll);  
12. int viewFocusY = (int)detector.getFocusY() - (v.getTop() + mYScroll);  
13. // Scroll to maintain the focus point  
14.                 mXScroll += viewFocusX - viewFocusX * scalingFactor;  
15.                 mYScroll += viewFocusY - viewFocusY * scalingFactor;  
16.                 requestLayout();  
17.             }  
18.         }  
19. return true;  
20. }

到此在pdf文件上签名就完成了,采用的方法也是比较局限,目前能匹配上的只限于手机的分辨率为720*1280,其他分辨手机没有做适配。由于每次是一边写文章,一边写代码,项目会存在很多不完善的,欢迎指正。在文章的最后,我会把今天的项目代码附上下载链接地址,需要的朋友可以下载分享。

二、总结:

在做对于pdf文件的签名时,查阅了很多资料,也没有看到相对较好的实例,结合自己的想法和网上的一些资料,一步一步最终实现了签名的效果,其中存在的问题也是很大,最主要的就是坐标匹配问题,放大页面就无法正常匹配了。之前想做的效果,就是直接在pdf页面进行操作,不过没有实现成功。对于手写签名,在很多领域都用到了,目前主要以收费为主,比如一些认证中心,他们在提供签证与验签时,也提供相关的签名过程。欢迎提出更好的实现方法,大家一起进步……