在使用HTML输入文档或使用ABCPDF导出为PDF后,如何创建空白的PDF签名字段? [英] How do I create a blank PDF signature field using an HTML input document or after exporting to PDF using ABCPDF?

查看:158
本文介绍了在使用HTML输入文档或使用ABCPDF导出为PDF后,如何创建空白的PDF签名字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个从ASPX生成的HTML源文件。

I have a source HTML document generated from an ASPX.

然后我使用ABCPDF将html转换为PDF。

I then convert the html to PDF using ABCPDF.

html有一个签名区域,它只是一个带边框的文本框。

The html has a signature area, which is just a text box with a border.

我需要在PDF内签名一个字段,其名称可以

I need a signature field inside the PDF with a name that I can pass on.

PDF将发送给与客户对应的第三方,然后对PDF进行数字签名并发回。

The PDF will be sent to a third-party who will correspond with the client and then digitally sign the PDF and send it back.

鉴于我有一个html文档或PDF,我该如何以编程方式在原始html签名区域周围添加空白的PDF签名字段,或以某种方式将两者联系起来?

Given that I have an html document, or PDF, how do I programmatically add the blank PDF signature field around the original html signature area or somehow relate the two?

这是一个示例应用程序,用于演示我正在做的一些事情:

Here is a sample application to demonstrate some of the things I'm doing:

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();
            }
        }
    }
}


推荐答案

希望这可以帮助其他人。

Hopefully this can help someone else.

我最终同时使用了ABCPDF和iTextSharp。

I ended up using both ABCPDF and iTextSharp.

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

I used ABCPDF to convert the HTML to PDF, and to tell me where the element is, and then I used iTextSharp to put the blank signature field over it.

此项目中有两个陷阱:


  1. ABCPDF更好地将HTML转换为PDF,因为它更宽容使用非标准html,并且当它们包含诸如 /而不是 \之类的小错误时,最好读取物理路径。

  2. 将html发送到PDF转换器时(iTextSharp或ABCPDF),需要将相对路径更改为物理路径,因为转换器将不知道您在哪个网站上运行或在哪个虚拟目录中查找图像,脚本和样式表。 (请参见下面的转换器,可以帮助您解决此问题。)

  3. ABCPDF更好地解释了样式表,并且使用更少的代码可以使最终结果更好。

  4. 当试图找出ABCPDF将字段或标记的元素放置在何处时,请记住,在添加第一页之后,您仍然必须进入循环以链接或注册其余页面,然后仅您将能够解析该字段或标记的元素。

  1. ABCPDF was better at converting HTML to PDF because it is more forgiving with non-standard html, and it was better at reading the physical paths when they contained small mistakes like "/" instead of "\".
  2. When sending your html to the PDF converter (iTextSharp or ABCPDF), the relative paths need to be changed into physical paths because the converter won't know in which web-site you are running or in which virtual directories to find the images, scripts and stylesheets. (See a converter below that can help with that)
  3. ABCPDF was better at interpreting the style sheets, and the end result looked much better with less code.
  4. When trying to figure out where ABCPDF placed the fields or tagged elements, bear in mind that after you add the first page, you still have to go into a loop to "chain" or register the rest of the pages, then only will you be able to resolve the field or the tagged element.

这里是一个示例项目,用于演示解决方案。

Here is a sample project to demonstrate the solution.

示例html :(请注意签名字段样式中的 abcpdf-tag-visible:true 部分,这将有助于我们查看元素的位置

The sample html: (notice the abcpdf-tag-visible: true part in the style of the signature field, this will help us to see where the element is placed in the 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打开。

Here is a screen shot of the PDF with a blank signature field, opened with Adobe afterwards.

一个示例控制台应用程序,可帮助测试PDF转换器:

A sample console application to help test out the PDF converters:

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)路径:

When running inside the web server and still generating the HTML, you can use this class to change the relative (virtual) paths to physical (UNC) paths:

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();

这篇关于在使用HTML输入文档或使用ABCPDF导出为PDF后,如何创建空白的PDF签名字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆