之前这个事情都CA公司去做的,现在给客户做demo,要模拟一下签字盖章了,我们的业务PDF文件是动态生成的所以没法通过坐标定位,只能通过关键字查找定位了。
之前在网上看了许多通多通过查询关键字,然后图片盖章的文章都不完整,说白了基本上没完成。我这边利用了网上查找关键字的方法。
我自己查看了相关Api,然后完善了这个功能。不多说了,直接上代码。
我这个只是示例,默认只取第一个关键字,多个相同关键字,根据业务场景定。 推荐方式:设置白色文字作为关键字,公司的业务我基本上都这样操作。
1.帮助类方法(关键字签字)
1 /// <summary>
2 /// pdf上图片签章
3 /// </summary>
4 public class SealPictureHelper
5 {
6 static float ReSizeMaxWidth = 30;
7 static float ReSizeMaxHeight = 30;
8 /// <summary>
9 /// 手写签字(流和base64格式)
10 /// </summary>
11 /// <param name="bytePdf">byte数组的pdf文件</param>
12 /// <param name="SignImgBase64">base64格式的图片</param>
13 /// <param name="SignKeyWord">关键字</param>
14 /// <returns></returns>
15 public static byte[] SignBase64Img(byte[] bytePdf, string SignImgBase64, string SignKeyWord)
16 {
17 byte[] newbytefile;
18 try
19 {
20 using (MemoryStream ms = new MemoryStream())
21 {
22 // 创建一个PdfReader对象
23 using (PdfReader reader = new PdfReader(bytePdf))
24 {
25 using (PdfStamper stamper = new PdfStamper(reader, ms))
26 {
27 // 获得文档页数
28 int n = reader.NumberOfPages;
29 for (int i = 1; i <= n; i++)
30 {
31 //获取当前页
32 PdfContentByte cb = stamper.GetOverContent(i);
33 PdfLocation pz = new PdfLocation();
34 iTextSharp.text.pdf.parser.PdfReaderContentParser p = new iTextSharp.text.pdf.parser.PdfReaderContentParser(reader);
35 p.ProcessContent<PdfLocation>(i, pz);
36 //查找当前页的关键字
37 pz.SearchKeywords(SignKeyWord);
38 if (pz.TextLocationInfo.Count > 0)
39 {
40 //坐标是从左下角往上,左下角为(0,0)零点
41 XTextInfo o = pz.TextLocationInfo[0];
42 //获取关键字左上角开始坐标
43 string[] L_T_Location = o.TopLeft.ToString().Split(',');//272.15,766.46,1
44 var left_x = float.Parse(L_T_Location[0]);
45 var top_y = float.Parse(L_T_Location[1]);
46 //获取关键字右下角结束坐标
47 string[] R_B_Location = o.BottomRight.ToString().Split(',');//305.15,755.46,1
48 var right_x = float.Parse(R_B_Location[0]);
49 var bottom_y = float.Parse(R_B_Location[1]);
50 //计算得到关键字的中心点
51 float x = (right_x - left_x) / 2 + left_x;
52 float y = (top_y - bottom_y) / 2 + bottom_y;
53 var imgtest = ConvertBase64ToImage(SignImgBase64);
54 //创建一个图片对象
55 iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(imgtest, System.Drawing.Imaging.ImageFormat.Jpeg);
56 //设置图片的指定大小
57 float expectWidth = img.Width;
58 float expectHeight = img.Height;
59 if (img.Width > img.Height && img.Width > ReSizeMaxWidth)
60 {
61 expectWidth = ReSizeMaxWidth;
62 expectHeight = expectWidth * img.Height / img.Width;
63 }
64 else if (img.Height > img.Width && img.Height > ReSizeMaxHeight)
65 {
66 expectHeight = ReSizeMaxHeight;
67 expectWidth = expectHeight * img.Width / img.Height;
68 }
69 img.ScaleToFit(expectWidth, expectHeight);//img.ScaleToFit(128, 128);
70 //设置图片位置在关键字正中心
71 img.SetAbsolutePosition(x - expectWidth / 2, y - expectHeight / 2);//
72 img.Transparency = new int[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
73 cb.AddImage(img);
74 }
75 }
76 }
77 }
78 newbytefile = ms.ToArray();
79 }
80 return newbytefile;
81 }
82 catch (Exception ex)
83 {
84 South.Tools.Logger.SaveLogUtil.Error(ex.Message, ex, "SignPicPDF");
85 return null;
86 }
87 }
88 /// <summary>
89 /// 手写签字(文件路径)
90 /// </summary>
91 /// <param name="Pdf_filePath">要签字的pdf文件路径</param>
92 /// <param name="SignImgPath">签字的图片路径</param>
93 /// <param name="SignKeyWord">关键字</param>
94 /// <returns></returns>
95 public static byte[] SignFile(string Pdf_filePath, string SignImgPath, string SignKeyWord)
96 {
97 byte[] newbytefile;
98 try
99 {
100 using (MemoryStream ms = new MemoryStream())
101 {
102 // 创建一个PdfReader对象
103 using (PdfReader reader = new PdfReader(Pdf_filePath))
104 {
105 using (PdfStamper stamper = new PdfStamper(reader, ms))
106 {
107 // 获得文档页数
108 int n = reader.NumberOfPages;
109 for (int i = 1; i <= n; i++)
110 {
111 //获取当前页
112 PdfContentByte cb = stamper.GetOverContent(i);
113 PdfLocation pz = new PdfLocation();
114 iTextSharp.text.pdf.parser.PdfReaderContentParser p = new iTextSharp.text.pdf.parser.PdfReaderContentParser(reader);
115 p.ProcessContent<PdfLocation>(i, pz);
116 //查找当前页的关键字
117 pz.SearchKeywords(SignKeyWord);
118 if (pz.TextLocationInfo.Count > 0)
119 {
120 XTextInfo o = pz.TextLocationInfo[0];
121 //获取关键字左上角开始坐标
122 string[] L_T_Location = o.TopLeft.ToString().Split(',');//272.15,766.46,1
123 var left_x = float.Parse(L_T_Location[0]);
124 var top_y = float.Parse(L_T_Location[1]);
125 //获取关键字右下角结束坐标
126 string[] R_B_Location = o.BottomRight.ToString().Split(',');//305.15,755.46,1
127 var right_x = float.Parse(R_B_Location[0]);
128 var bottom_y = float.Parse(R_B_Location[1]);
129 //计算得到关键字的中心点
130 float x = (right_x - left_x) / 2 + left_x;
131 float y = (top_y - bottom_y) / 2 + bottom_y;
132 //创建一个图片对象
133 iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(SignImgPath);
134 float expectWidth = img.Width;
135 float expectHeight = img.Height;
136 if (img.Width > img.Height && img.Width > ReSizeMaxWidth)
137 {
138 expectWidth = ReSizeMaxWidth;
139 expectHeight = expectWidth * img.Height / img.Width;
140 }
141 else if (img.Height > img.Width && img.Height > ReSizeMaxHeight)
142 {
143 expectHeight = ReSizeMaxHeight;
144 expectWidth = expectHeight * img.Width / img.Height;
145 }
146 //设置图片的指定大小
147 img.ScaleToFit(expectWidth, expectHeight);//img.ScaleToFit(128, 128);
148 //设置图片位置在关键字正中心
149 img.SetAbsolutePosition(x - expectWidth / 2, y - expectHeight / 2);//
150 img.Transparency = new int[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
151 cb.AddImage(img);
152 }
153 }
154 }
155 }
156 newbytefile = ms.ToArray();
157 }
158 return newbytefile;
159 }
160 catch (Exception ex)
161 {
162 South.Tools.Logger.SaveLogUtil.Error(ex.Message, ex, "SignPicPDF");
163 return null;
164 }
165 }
166
167 public static System.Drawing.Image ConvertBase64ToImage(string base64String)
168 {
169 byte[] imageBytes = Convert.FromBase64String(base64String);
170 System.Drawing.Bitmap bitmap = null;
171 MemoryStream stream = null;
172 try
173 {
174 stream = new MemoryStream(imageBytes);
175 bitmap = new System.Drawing.Bitmap(stream);
176 //bitmap.Save(IOHelper.getPhysicalDir() + "/test.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
177 return bitmap;
178 }
179 catch (Exception)
180 {
181
182 throw;
183 }
184 finally
185 {
186
187 //if (stream != null)
188 //{
189 // stream.Dispose();
190 //}
191 }
192
193 }
194 }
2.查找关键字相关类
1 public class PdfLocation : LocationTextExtractionStrategy
2 {
3 private List<XTextChunk> m_locationResult = new List<XTextChunk>();
4 private List<XTextInfo> m_TextLocationInfo = new List<XTextInfo>();
5 public List<XTextChunk> LocationResult
6 {
7 get { return m_locationResult; }
8 }
9 public List<XTextInfo> TextLocationInfo
10 {
11 get { return m_TextLocationInfo; }
12 }
13
14 /// <summary>
15 /// Creates a new LocationTextExtracationStrategyEx
16 /// </summary>
17 public PdfLocation()
18 {
19 }
20 public void SearchKeywords(string sKeyword, bool bDefaultFirst = true)
21 {
22 m_locationResult.Sort();
23 m_TextLocationInfo.Clear();
24 //StringBuilder sb = new StringBuilder();
25 XTextChunk lastChunk = null;
26 XTextInfo lastXTextInfo = null;
27 foreach (XTextChunk chunk in m_locationResult)
28 {
29 if (lastChunk == null)
30 {
31 //sb.Append(chunk.Text);
32 lastXTextInfo = new XTextInfo(chunk);
33 //if (lastXTextInfo.Text.Contains(sKeyword))
34 //{
35 // m_TextLocationInfo.Add(lastXTextInfo);
36 //}
37 }
38 else
39 {
40 if (chunk.sameLine(lastChunk))
41 {
42 float dist = chunk.distanceFromEndOf(lastChunk);
43
44 if (dist < -chunk.CharSpaceWidth)
45 {
46 //sb.Append(' ');
47 lastXTextInfo.addSpace();
48 }
49 //append a space if the trailing char of the prev string wasn't a space && the 1st char of the current string isn't a space
50 else if (dist > chunk.CharSpaceWidth / 2.0f && chunk.Text[0] != ' ' && lastChunk.Text[lastChunk.Text.Length - 1] != ' ')
51 {
52 //sb.Append(' ');
53 lastXTextInfo.addSpace();
54 }
55 //sb.Append(chunk.Text);
56 lastXTextInfo.appendText(chunk);
57 }
58 else
59 {
60 //sb.Append('\n');
61 //sb.Append(chunk.Text);
62 lastXTextInfo = new XTextInfo(chunk);
63 //if (lastXTextInfo.Text.Contains(sKeyword))
64 //{
65 // m_TextLocationInfo.Add(lastXTextInfo);
66 //}
67 }
68 }
69 lastChunk = chunk;
70 if (lastXTextInfo.Text.Contains(sKeyword))
71 {
72 m_TextLocationInfo.Add(lastXTextInfo);
73 break;
74 }
75 }
76 }
77 /// <summary>
78 /// Returns the result so far
79 /// </summary>
80 /// <returns>a String with the resulting text</returns>
81 public override String GetResultantText()
82 {
83 m_locationResult.Sort();
84
85 StringBuilder sb = new StringBuilder();
86 XTextChunk lastChunk = null;
87 XTextInfo lastXTextInfo = null;
88 foreach (XTextChunk chunk in m_locationResult)
89 {
90 if (lastChunk == null)
91 {
92 sb.Append(chunk.Text);
93 lastXTextInfo = new XTextInfo(chunk);
94 m_TextLocationInfo.Add(lastXTextInfo);
95 }
96 else
97 {
98 if (chunk.sameLine(lastChunk))
99 {
100 float dist = chunk.distanceFromEndOf(lastChunk);
101
102 if (dist < -chunk.CharSpaceWidth)
103 {
104 sb.Append(' ');
105 lastXTextInfo.addSpace();
106 }
107 //append a space if the trailing char of the prev string wasn't a space && the 1st char of the current string isn't a space
108 else if (dist > chunk.CharSpaceWidth / 2.0f && chunk.Text[0] != ' ' && lastChunk.Text[lastChunk.Text.Length - 1] != ' ')
109 {
110 sb.Append(' ');
111 lastXTextInfo.addSpace();
112 }
113 sb.Append(chunk.Text);
114 lastXTextInfo.appendText(chunk);
115 }
116 else
117 {
118 sb.Append('\n');
119 sb.Append(chunk.Text);
120 lastXTextInfo = new XTextInfo(chunk);
121 m_TextLocationInfo.Add(lastXTextInfo);
122 }
123 }
124 lastChunk = chunk;
125 }
126 return sb.ToString();
127 }
128
129 /// <summary>
130 ///
131 /// </summary>
132 /// <param name="renderInfo"></param>
133 public override void RenderText(TextRenderInfo renderInfo)
134 {
135 LineSegment segment = renderInfo.GetBaseline();
136 XTextChunk location = new XTextChunk(renderInfo.GetText(), segment.GetStartPoint(), segment.GetEndPoint(), renderInfo.GetSingleSpaceWidth(), renderInfo.GetAscentLine(), renderInfo.GetDescentLine());
137 m_locationResult.Add(location);
138 }
139
140
141 }
142 public class XTextChunk : IComparable, ICloneable
143 {
144 string m_text;
145 Vector m_startLocation;
146 Vector m_endLocation;
147 Vector m_orientationVector;
148 int m_orientationMagnitude;
149 int m_distPerpendicular;
150 float m_distParallelStart;
151 float m_distParallelEnd;
152 float m_charSpaceWidth;
153
154 public LineSegment AscentLine;
155 public LineSegment DecentLine;
156
157 public object Clone()
158 {
159 XTextChunk copy = new XTextChunk(m_text, m_startLocation, m_endLocation, m_charSpaceWidth, AscentLine, DecentLine);
160 return copy;
161 }
162
163 public string Text
164 {
165 get { return m_text; }
166 set { m_text = value; }
167 }
168 public float CharSpaceWidth
169 {
170 get { return m_charSpaceWidth; }
171 set { m_charSpaceWidth = value; }
172 }
173 public Vector StartLocation
174 {
175 get { return m_startLocation; }
176 set { m_startLocation = value; }
177 }
178 public Vector EndLocation
179 {
180 get { return m_endLocation; }
181 set { m_endLocation = value; }
182 }
183
184 /// <summary>
185 /// Represents a chunk of text, it's orientation, and location relative to the orientation vector
186 /// </summary>
187 /// <param name="txt"></param>
188 /// <param name="startLoc"></param>
189 /// <param name="endLoc"></param>
190 /// <param name="charSpaceWidth"></param>
191 public XTextChunk(string txt, Vector startLoc, Vector endLoc, float charSpaceWidth, LineSegment ascentLine, LineSegment decentLine)
192 {
193 m_text = txt;
194 m_startLocation = startLoc;
195 m_endLocation = endLoc;
196 m_charSpaceWidth = charSpaceWidth;
197 AscentLine = ascentLine;
198 DecentLine = decentLine;
199
200 m_orientationVector = m_endLocation.Subtract(m_startLocation).Normalize();
201 m_orientationMagnitude = (int)(Math.Atan2(m_orientationVector[Vector.I2], m_orientationVector[Vector.I1]) * 1000);
202
203 // the two vectors we are crossing are in the same plane, so the result will be purely
204 // in the z-axis (out of plane) direction, so we just take the I3 component of the result
205 Vector origin = new Vector(0, 0, 1);
206 m_distPerpendicular = (int)(m_startLocation.Subtract(origin)).Cross(m_orientationVector)[Vector.I3];
207
208 m_distParallelStart = m_orientationVector.Dot(m_startLocation);
209 m_distParallelEnd = m_orientationVector.Dot(m_endLocation);
210 }
211
212 /// <summary>
213 /// true if this location is on the the same line as the other text chunk
214 /// </summary>
215 /// <param name="XTextChunkToCompare">the location to compare to</param>
216 /// <returns>true if this location is on the the same line as the other</returns>
217 public bool sameLine(XTextChunk XTextChunkToCompare)
218 {
219 if (m_orientationMagnitude != XTextChunkToCompare.m_orientationMagnitude) return false;
220 if (m_distPerpendicular != XTextChunkToCompare.m_distPerpendicular) return false;
221 return true;
222 }
223
224 /// <summary>
225 /// Computes the distance between the end of 'other' and the beginning of this chunk
226 /// in the direction of this chunk's orientation vector. Note that it's a bad idea
227 /// to call this for chunks that aren't on the same line and orientation, but we don't
228 /// explicitly check for that condition for performance reasons.
229 /// </summary>
230 /// <param name="other"></param>
231 /// <returns>the number of spaces between the end of 'other' and the beginning of this chunk</returns>
232 public float distanceFromEndOf(XTextChunk other)
233 {
234 float distance = m_distParallelStart - other.m_distParallelEnd;
235 return distance;
236 }
237
238 /// <summary>
239 /// Compares based on orientation, perpendicular distance, then parallel distance
240 /// </summary>
241 /// <param name="obj"></param>
242 /// <returns></returns>
243 public int CompareTo(object obj)
244 {
245 if (obj == null) throw new ArgumentException("Object is now a XTextChunk");
246
247 XTextChunk rhs = obj as XTextChunk;
248 if (rhs != null)
249 {
250 if (this == rhs) return 0;
251
252 int rslt;
253 rslt = m_orientationMagnitude - rhs.m_orientationMagnitude;
254 if (rslt != 0) return rslt;
255
256 rslt = m_distPerpendicular - rhs.m_distPerpendicular;
257 if (rslt != 0) return rslt;
258
259 // note: it's never safe to check floating point numbers for equality, and if two chunks
260 // are truly right on top of each other, which one comes first or second just doesn't matter
261 // so we arbitrarily choose this way.
262 rslt = m_distParallelStart < rhs.m_distParallelStart ? -1 : 1;
263
264 return rslt;
265 }
266 else
267 {
268 throw new ArgumentException("Object is now a XTextChunk");
269 }
270 }
271 }
272
273 public class XTextInfo
274 {
275 public Vector TopLeft;
276 public Vector BottomRight;
277 private string m_Text;
278
279 public string Text
280 {
281 get { return m_Text; }
282 }
283
284 /// <summary>
285 /// Create a XTextInfo.
286 /// </summary>
287 /// <param name="initialXTextChunk"></param>
288 public XTextInfo(XTextChunk initialXTextChunk)
289 {
290 TopLeft = initialXTextChunk.AscentLine.GetStartPoint();
291 BottomRight = initialXTextChunk.DecentLine.GetEndPoint();
292 //TopLeft = initialXTextChunk.StartLocation;
293 //BottomRight = initialXTextChunk.EndLocation;
294 m_Text = initialXTextChunk.Text;
295 }
296
297 /// <summary>
298 /// Add more text to this XTextInfo.
299 /// </summary>
300 /// <param name="additionalXTextChunk"></param>
301 public void appendText(XTextChunk additionalXTextChunk)
302 {
303 BottomRight = additionalXTextChunk.DecentLine.GetEndPoint();
304 //BottomRight = additionalXTextChunk.EndLocation;
305 m_Text += additionalXTextChunk.Text;
306 }
307
308 /// <summary>
309 /// Add a space to the XTextInfo. This will leave the endpoint out of sync with the text.
310 /// The assumtion is that you will add more text after the space which will correct the endpoint.
311 /// </summary>
312 public void addSpace()
313 {
314 m_Text += ' ';
315 }
316 }