我有一个从ASPX生成的源HTML文档。
然后我使用ABCPDF将html转换为PDF。
html有一个签名区域,它只是一个带边框的文本框。
我需要PDF中的签名字段,其中包含我可以传递的名称。
PDF将发送给与客户对应的第三方,然后对PDF进行数字签名并将其发回。
鉴于我有一个html文档或PDF,如何以编程方式在原始html签名区域周围添加空白PDF签名字段或以某种方式将两者联系起来?
这是一个示例应用程序,用于演示我正在做的一些事情:
namespace ABCPDFHtmlSignatureTest
{
using System.Diagnostics;
using System.IO;
using System.Reflection;
using WebSupergoo.ABCpdf8;
using WebSupergoo.ABCpdf8.Objects;
/// <summary>
/// The program.
/// </summary>
public class Program
{
/// <summary>
/// The file name.
/// </summary>
private const string FileName = @"c:\temp\pdftest.pdf";
/// <summary>
/// The application entry point.
/// </summary>
/// <param name="args">
/// The args.
/// </param>
public static void Main(string[] args)
{
var html = GetHtml();
var pdf = GetPdf(html);
/* save the PDF to disk */
File.WriteAllBytes(FileName, pdf);
/* open the PDF */
Process.Start(FileName);
}
/// <summary>
/// The get PDF.
/// </summary>
/// <param name="html">
/// The html.
/// </param>
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
public static byte[] GetPdf(string html)
{
var document = new Doc();
/* Yes, generate PDF fields for the html form inputs */
document.HtmlOptions.AddForms = true;
document.AddImageHtml(html);
/* We can determine the location of the field */
var signatureRect = document.Form["Signature"].Rect;
MakeFieldsReadOnly(document.Form.Fields);
return document.GetData();
}
/// <summary>
/// The get html.
/// </summary>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
public static string GetHtml()
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ABCPDFHtmlSignatureTest.HTMLPage1.html"))
{
using (var streamReader = new StreamReader(stream))
{
return streamReader.ReadToEnd();
}
}
}
/// <summary>
/// The make fields read only.
/// </summary>
/// <param name="fields">
/// The fields.
/// </param>
private static void MakeFieldsReadOnly(Fields fields)
{
foreach (var field in fields)
{
if (field.Name == "Signature") continue;
field.Stamp();
}
}
}
}
答案 0 :(得分:1)
希望这可以帮助别人。
我最终使用了ABCPDF和iTextSharp。
我使用ABCPDF将HTML转换为PDF,并告诉我元素的位置,然后我使用iTextSharp将空白签名字段放在上面。
这个项目中有一些“陷阱”:
以下是演示解决方案的示例项目。
示例html :(注意签名字段样式中的 abcpdf-tag-visible:true 部分,这将有助于我们查看元素在PDF中的位置)
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Test Document</title>
</head>
<body>
<form method="POST">
Sample Report Data: <br />
<table style="border: solid 1px red; margin: 5px" cellpadding="5px">
<tr>
<td>Field 1:</td>
<td><input type="text" id="field1" name="field1" value="FIELD1VALUE" /></td>
</tr>
<tr>
<td>Field 2:</td>
<td><input type="text" id="Text2" value="FIELD2VALUE" /></td>
</tr>
<tr>
<td>Field 3:</td>
<td><input type="text" id="Text3" value="FIELD3VALUE" /></td>
</tr>
<tr>
<td>Field 4:</td>
<td><input type="text" id="Text4" value="FIELD4VALUE" /></td>
</tr>
<tr>
<td>Signature:</td>
<td><textarea id="ClientSignature" style="background-color:LightCyan;border-color:Gray;border-width:1px;border-style:Solid;height:50px;width:200px;abcpdf-tag-visible: true"
rows="2" cols="20"></textarea></td>
</tr>
</table>
</form>
</body>
</html>
以下是带有空白签名字段的PDF的屏幕截图,之后用Adobe打开。
帮助测试PDF转换器的示例控制台应用程序:
namespace ABCPDFHtmlSignatureTest
{
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using iTextSharp.text;
using iTextSharp.text.pdf;
using WebSupergoo.ABCpdf8;
/// <summary>
/// The program.
/// </summary>
public class Program
{
/// <summary>
/// The file name.
/// </summary>
private const string PdfFileName = @"c:\temp\pdftest.pdf";
/// <summary>
/// Adds a blank signature field at the specified location.
/// </summary>
/// <param name="pdf">The PDF.</param>
/// <param name="signatureRect">The signature location.</param>
/// <param name="signaturePage">the page on which the signature appears</param>
/// <returns>The new PDF.</returns>
private static byte[] AddBlankSignatureField(byte[] pdf, Rectangle signatureRect, int signaturePage)
{
var pdfReader = new PdfReader(pdf);
using (var ms = new MemoryStream())
{
var pdfStamper = new PdfStamper(pdfReader, ms);
var signatureField = PdfFormField.CreateSignature(pdfStamper.Writer);
signatureField.SetWidget(signatureRect, null);
signatureField.Flags = PdfAnnotation.FLAGS_PRINT;
signatureField.Put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g"));
signatureField.FieldName = "ClientSignature";
signatureField.Page = signaturePage;
pdfStamper.AddAnnotation(signatureField, signaturePage);
pdfStamper.Close();
return ms.ToArray();
}
}
/// <summary>
/// The application entry point.
/// </summary>
/// <param name="args">
/// The args.
/// </param>
public static void Main(string[] args)
{
var html = GetHtml();
XRect signatureRect;
int signaturePage;
byte[] pdf;
GetPdfUsingAbc(html, out pdf, out signatureRect, out signaturePage);
/* convert to type that iTextSharp needs */
var signatureRect2 = new Rectangle(
Convert.ToSingle(signatureRect.Left),
Convert.ToSingle(signatureRect.Top),
Convert.ToSingle(signatureRect.Right),
Convert.ToSingle(signatureRect.Bottom));
pdf = AddBlankSignatureField(pdf, signatureRect2, signaturePage);
/* save the PDF to disk */
File.WriteAllBytes(PdfFileName, pdf);
/* open the PDF */
Process.Start(PdfFileName);
}
/// <summary>
/// Returns the PDF for the specified html. The conversion is done using ABCPDF.
/// </summary>
/// <param name="html">The html.</param>
/// <param name="pdf">the PDF</param>
/// <param name="signatureRect">the location of the signature field</param>
/// <param name="signaturePage">the page of the signature field</param>
public static void GetPdfUsingAbc(string html, out byte[] pdf, out XRect signatureRect, out int signaturePage)
{
var document = new Doc();
document.MediaBox.String = "A4";
document.Color.String = "255 255 255";
document.FontSize = 7;
/* tag elements marked with "abcpdf-tag-visible: true" */
document.HtmlOptions.AddTags = true;
int pageId = document.AddImageHtml(html, true, 950, true);
int pageNumber = 1;
signatureRect = null;
signaturePage = -1;
TryIdentifySignatureLocationOnCurrentPage(document, pageId, pageNumber, ref signatureRect, ref signaturePage);
while (document.Chainable(pageId))
{
document.Page = document.AddPage();
pageId = document.AddImageToChain(pageId);
pageNumber++;
TryIdentifySignatureLocationOnCurrentPage(document, pageId, pageNumber, ref signatureRect, ref signaturePage);
}
pdf = document.GetData();
}
/// <summary>
/// The try identify signature location on current page.
/// </summary>
/// <param name="document">The document.</param>
/// <param name="currentPageId">The current page id.</param>
/// <param name="currentPageNumber">The current page number.</param>
/// <param name="signatureRect">The signature location.</param>
/// <param name="signaturePage">The signature page.</param>
private static void TryIdentifySignatureLocationOnCurrentPage(Doc document, int currentPageId, int currentPageNumber, ref XRect signatureRect, ref int signaturePage)
{
if (null != signatureRect) return;
var tagIds = document.HtmlOptions.GetTagIDs(currentPageId);
if (tagIds.Length > 0)
{
int index = -1;
foreach (var tagId in tagIds)
{
index++;
if (tagId.Contains("ClientSignature"))
{
var rects = document.HtmlOptions.GetTagRects(currentPageId);
signatureRect = rects[index];
signaturePage = currentPageNumber;
break;
}
}
}
}
/// <summary>
/// The get html.
/// </summary>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
public static string GetHtml()
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ABCPDFHtmlSignatureTest.HTMLPage1.html"))
{
if (null == stream)
{
throw new InvalidOperationException("Unable to resolve the html");
}
using (var streamReader = new StreamReader(stream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
在Web服务器内运行并仍然生成HTML时,您可以使用此类更改物理(UNC)路径的相对(虚拟)路径:
namespace YourNameSpace
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
/// <summary>
/// Replaces all uris within in an html document to physical paths, making it valid
/// html outside the context of a web site. This is necessary because outside the
/// context of a web site root folder, the uris are meaningless, and the html cannot
/// be interpreted correctly by external components, like ABCPDF or iTextSharp.
/// Without this step, the images and other 'SRC' references cannot be resolved.
/// </summary>
public sealed class HtmlRelativeToPhysicalPathConverter
{
#region FIELDS
/// <summary>
/// The _server.
/// </summary>
private readonly HttpServerUtility _server;
/// <summary>
/// The _html.
/// </summary>
private readonly string _html;
#endregion
#region CONSTRUCTOR
/// <summary>
/// Initialises a new instance of the <see cref="HtmlRelativeToPhysicalPathConverter"/> class.
/// </summary>
/// <param name="server">
/// The server.
/// </param>
/// <param name="html">
/// The html.
/// </param>
/// <exception cref="ArgumentNullException">
/// when <paramref name="server"/> or <paramref name="html"/> is null or empty.
/// </exception>
public HtmlRelativeToPhysicalPathConverter(HttpServerUtility server, string html)
{
if (null == server) throw new ArgumentNullException("server");
if (string.IsNullOrWhiteSpace(html)) throw new ArgumentNullException("html");
_server = server;
_html = html;
}
#endregion
#region Convert Html
/// <summary>
/// Convert the html.
/// </summary>
/// <param name="leaveUrisIfFileCannotBeFound">an additional validation can be performed before changing the uri to a directory path</param>
/// <returns>The converted html with physical paths in all uris.</returns>
public string ConvertHtml(bool leaveUrisIfFileCannotBeFound = false)
{
var htmlBuilder = new StringBuilder(_html);
// Double quotes
foreach (var relativePath in this.GetRelativePaths(htmlBuilder, '"'))
{
this.ReplaceRelativePath(htmlBuilder, relativePath, leaveUrisIfFileCannotBeFound);
}
// Single quotes
foreach (var relativePath in this.GetRelativePaths(htmlBuilder, '\''))
{
this.ReplaceRelativePath(htmlBuilder, relativePath, leaveUrisIfFileCannotBeFound);
}
return htmlBuilder.ToString();
}
#endregion
#region Replace Relative Path
/// <summary>
/// Convert a uri to the physical path.
/// </summary>
/// <param name="htmlBuilder">The html builder.</param>
/// <param name="relativePath">The relative path or uri string.</param>
/// <param name="leaveUrisIfFileCannotBeFound">an additional validation can be performed before changing the uri to a directory path</param>
private void ReplaceRelativePath(StringBuilder htmlBuilder, string relativePath, bool leaveUrisIfFileCannotBeFound)
{
try
{
var parts = relativePath.Split('?');
var mappedPath = _server.MapPath(parts[0]);
if ((leaveUrisIfFileCannotBeFound && File.Exists(mappedPath)) || !leaveUrisIfFileCannotBeFound)
{
if (parts.Length > 1)
{
mappedPath += "?" + parts[1];
}
htmlBuilder.Replace(relativePath, mappedPath);
}
else
{
/* decide what you want to do with these */
}
}
catch (ArgumentException)
{
/* ignore these */
}
}
#endregion
#region Get Relative Paths
/// <summary>
/// They are NOT guaranteed to be valid uris, simply values between quote characters.
/// </summary>
/// <param name="html">the html builder</param>
/// <param name="quoteChar">the quote character to use, e.g. " or '</param>
/// <returns>each of the relative paths</returns>
private IEnumerable<string> GetRelativePaths(StringBuilder html, char quoteChar)
{
var position = 0;
var oldPosition = -1;
var htmlString = html.ToString();
var previousUriString = string.Empty;
while (oldPosition != position)
{
oldPosition = position;
position = htmlString.IndexOf(quoteChar, position + 1);
if (position == -1) break;
var uriString = htmlString.Substring(oldPosition + 1, (position - oldPosition) - 1);
if (Uri.IsWellFormedUriString(uriString, UriKind.Relative)
&& uriString != previousUriString
/* as far as I know we never reference a file without an extension, so avoid the IDs this way */
&& uriString.Contains(".") && !uriString.EndsWith("."))
{
yield return uriString;
/* refresh the html string, and reiterate again */
htmlString = html.ToString();
position = oldPosition;
oldPosition = position - 1; /* don't exit yet */
previousUriString = uriString;
}
}
}
#endregion
}
}
你可以使用这样的类:
var html = textWriter.ToString();
// change relative paths to be absolute
html = new HtmlRelativeToPhysicalPathConverter(server, html).ConvertHtml();