如何使用HTML输入文档或使用ABCPDF导出为PDF后创建空白PDF签名字段?

时间:2013-12-17 17:12:08

标签: c# pdf pdf-generation digital-signature html-to-pdf

我有一个从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();
            }
        }
    }
}

1 个答案:

答案 0 :(得分:1)

希望这可以帮助别人。

我最终使用了ABCPDF和iTextSharp。

我使用ABCPDF将HTML转换为PDF,并告诉我元素的位置,然后我使用iTextSharp将空白签名字段放在上面。

这个项目中有一些“陷阱”:

  1. ABCPDF更擅长将HTML转换为PDF,因为它对非标准html更加宽容,并且当它们包含像“/”而不是“\”这样的小错误时,它更好地阅读物理路径。
  2. 将html发送到PDF转换器(iTextSharp或ABCPDF)时,需要将相对路径更改为物理路径,因为转换器不知道您正在运行的网站或要查找的虚拟目录图像,脚本和样式表。 (参见下面的转换器可以帮助解决这个问题)
  3. ABCPDF更擅长解释样式表,最终结果看起来更好,代码更少。
  4. 当试图找出ABCPDF放置字段或标记元素的位置时,请记住在添加第一页后,您仍然需要进入循环“链接”或注册其余页面,然后才能你能解析字段或标记元素吗?
  5. 以下是演示解决方案的示例项目。

    示例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打开。

    pdfresultscreenshot

    帮助测试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();