在 iTextSharp 5.4.5.0 中复制字段

Posted

技术标签:

【中文标题】在 iTextSharp 5.4.5.0 中复制字段【英文标题】:Copying fields in iTextSharp 5.4.5.0 【发布时间】:2014-02-11 21:35:02 【问题描述】:

我的印象是现在可以使用 PdfCopy 复制 AcroFields。在release notes for iText 5.4.4.0 中,现在已尽可能列出。但是,当我尝试这样做时,字段的所有注释(我认为我正确使用了该术语,对 iText 来说仍然相当新……)似乎都被删除了。看起来字段在那里(意味着我可以看到指示可编辑字段的蓝色框),但它们不可编辑。如果我尝试在 Acrobat 中打开 PDF,我会收到一条消息,提示“没有字段,您希望 Acrobat 发现它们吗?”并且大多数都可以正确找到并标记和字段(复选框不是,但文本字段是)。

我假设有一个额外的步骤可以将注释重新添加到 PdfCopy 对象,但我看不到从 PdfReader 获取注释的方法。我似乎也找不到任何有关如何执行此操作的文档(因为 AcroFields 在 PdfCopy 中长期以来不受支持,因此我发现的大部分内容都是如此)。

由于敏感性,我无法提供相关 PDF 的副本,但使用之前使用的测试程序的更改版本,您可以通过以下代码看到问题。它应该生成一个表格,其中四个右列中有一些复选框。如果我在 MergePdfs 方法中使用与 PdfCopyFields 完全相同的代码而不是 PdfCopy,它会按预期工作。此代码不会生成任何文本字段,但在我的主项目中,它们是用作模板的原始父 PDF 的一部分。

(对不起,这个例子很长,它是从一个更大的应用程序中挑选出来的。您需要一个 PDF,其中某处有一个名为“TableStartPosition”的字段,并使用本地计算机的正确路径更新 RunTest 以获得此工作。)

PdfCopy 功能还没有进入 iTextSharp 吗?我使用的是 5.4.5.0 版本。

class Program

    Stream _pdfTemplateStream;
    MemoryStream _pdfResultStream;

    PdfReader _pdfTemplateReader;
    PdfStamper _pdfResultStamper;

    static void Main(string[] args)
    
        Program p = new Program();
        try
        
            p.RunTest();
        
        catch (Exception f)
        
            Console.WriteLine(f.Message);
            Console.ReadLine();
        
    
    internal void RunTest()
    
        FileStream fs = File.OpenRead(@"C:\temp\a\RenameFieldTest\RenameFieldTest\Library\CoverPage.pdf");
        _pdfTemplateStream = fs;
        _pdfResultStream = new MemoryStream();
        //PDFTemplateStream = new FileStream(_templatePath, FileMode.Open);
        _pdfTemplateReader = new PdfReader(_pdfTemplateStream);
        _pdfResultStamper = new PdfStamper(_pdfTemplateReader, _pdfResultStream);

        #region setup objects
        List<CustomCategory> Categories = new List<CustomCategory>();
        CustomCategory c1 = new CustomCategory();
        c1.CategorySizesInUse.Add(CustomCategory.AvailableSizes[1]);
        c1.CategorySizesInUse.Add(CustomCategory.AvailableSizes[2]);
        Categories.Add(c1);

        CustomCategory c2 = new CustomCategory();
        c2.CategorySizesInUse.Add(CustomCategory.AvailableSizes[0]);
        c2.CategorySizesInUse.Add(CustomCategory.AvailableSizes[1]);
        Categories.Add(c2);

        List<CustomObject> Items = new List<CustomObject>();
        CustomObject co1 = new CustomObject();
        co1.Category = c1;
        co1.Title = "Object 1";
        Items.Add(co1);

        CustomObject co2 = new CustomObject();
        co2.Category = c2;
        co2.Title = "Object 2";
        Items.Add(co2);

        #endregion

        FillCoverPage(Items);
        _pdfResultStamper.Close();
        _pdfTemplateReader.Close();

        List<MemoryStream> pdfStreams = new List<MemoryStream>();
        pdfStreams.Add(new MemoryStream(_pdfResultStream.ToArray()));

        MergePdfs(@"C:\temp\a\RenameFieldTest\RenameFieldTest\Library\Outfile.pdf", pdfStreams);

        _pdfResultStream.Dispose();
        _pdfTemplateStream.Dispose();
    
    internal void FillCoverPage(List<CustomObject> Items)
    

        //Before we start we need to figure out where to start adding the table
        var fieldPositions = _pdfResultStamper.AcroFields.GetFieldPositions("TableStartPosition");
        if (fieldPositions == null)
         throw new Exception("Could not find the TableStartPosition field. Unable to determine point of origin for the table!"); 

        _pdfResultStamper.AcroFields.RemoveField("TableStartPosition");

        var fieldPosition = fieldPositions[0];
        // Get the position of the field
        var targetPosition = fieldPosition.position;

        //First, get all the available card sizes
        List<string> availableSizes = CustomCategory.AvailableSizes;


        //Generate a table with the number of available card sizes + 1 for the device name
        PdfPTable table = new PdfPTable(availableSizes.Count + 1);
        float[] columnWidth = new float[availableSizes.Count + 1];
        for (int y = 0; y < columnWidth.Length; y++)
        
            if (y == 0)
             columnWidth[y] = 320; 
            else
             columnWidth[y] = 120; 
        

        table.SetTotalWidth(columnWidth);
        table.WidthPercentage = 100;

        PdfContentByte canvas;

        List<PdfFormField> checkboxes = new List<PdfFormField>();

        //Build the header row
        table.Rows.Add(new PdfPRow(this.GetTableHeaderRow(availableSizes)));

        //Insert the global check boxes
        PdfPCell[] globalRow = new PdfPCell[availableSizes.Count + 1];
        Phrase tPhrase = new Phrase("Select/Unselect All");
        PdfPCell tCell = new PdfPCell();
        tCell.BackgroundColor = BaseColor.LIGHT_GRAY;
        tCell.AddElement(tPhrase);
        globalRow[0] = tCell;

        for (int x = 0; x < availableSizes.Count; x++)
        
            tCell = new PdfPCell();
            tCell.BackgroundColor = BaseColor.LIGHT_GRAY;
            PdfFormField f = PdfFormField.CreateCheckBox(_pdfResultStamper.Writer);
            string fieldName = string.Format("InkSaver.Global.chk0", availableSizes[x].Replace(".", ""));
            //f.FieldName = fieldName;
            string js = string.Format("hideAll(event.target, '0');", availableSizes[x].Replace(".", ""));
            f.Action = PdfAction.javascript(js, _pdfResultStamper.Writer);
            tCell.CellEvent = new ChildFieldEvent(_pdfResultStamper.Writer, f, fieldName);
            globalRow[x + 1] = tCell;
            checkboxes.Add(f);
        
        table.Rows.Add(new PdfPRow(globalRow));

        int status = 0;
        int pageNum = 1;

        for (int itemIndex = 0; itemIndex < Items.Count; itemIndex++)
        
            tCell = new PdfPCell();
            Phrase p = new Phrase(Items[itemIndex].Title);
            tCell.AddElement(p);
            tCell.HorizontalAlignment = Element.ALIGN_LEFT;

            PdfPCell[] cells = new PdfPCell[availableSizes.Count + 1];
            cells[0] = tCell;

            for (int availCardSizeIndex = 0; availCardSizeIndex < availableSizes.Count; availCardSizeIndex++)
            
                if (Items[itemIndex].Category.CategorySizesInUse.Contains(availableSizes[availCardSizeIndex]))
                
                    string str = availableSizes[availCardSizeIndex];
                    tCell = new PdfPCell();
                    tCell.PaddingLeft = 10f;
                    tCell.PaddingRight = 10f;
                    cells[availCardSizeIndex + 1] = tCell;
                    cells[availCardSizeIndex].HorizontalAlignment = Element.ALIGN_CENTER;

                    PdfFormField f = PdfFormField.CreateCheckBox(_pdfResultStamper.Writer);
                    string fieldName = string.Format("InkSaver.chk0.1", availableSizes[availCardSizeIndex].Replace(".", ""), itemIndex + 1);
                    //f.FieldName = fieldName; <-- This causes the checkbox to be double-named (i.e. InkSaver.Global.chk0.InkSaver.Global.chk0
                    string js = string.Format("hideCardSize(event.target, 0, '1');", itemIndex + 1, availableSizes[availCardSizeIndex]);
                    f.Action = PdfAction.JavaScript(js, _pdfResultStamper.Writer);
                    tCell.CellEvent = new ChildFieldEvent(_pdfResultStamper.Writer, f, fieldName);

                    checkboxes.Add(f);
                
                else
                
                    //Add a blank cell
                    tCell = new PdfPCell();
                    cells[availCardSizeIndex + 1] = tCell;
                
            
            //Test if the column text will fit

            table.Rows.Add(new PdfPRow(cells));

            canvas = _pdfResultStamper.GetUnderContent(pageNum);
            ColumnText ct2 = new ColumnText(canvas);
            ct2.AddElement(new PdfPTable(table));
            ct2.Alignment = Element.ALIGN_LEFT;
            ct2.SetSimpleColumn(targetPosition.Left, 0, targetPosition.Right, targetPosition.Top, 0, 0);
            status = ct2.Go(true);

            if ((status != ColumnText.NO_MORE_TEXT) || (itemIndex == (Items.Count - 1)))
            
                ColumnText ct3 = new ColumnText(canvas);
                ct3.AddElement(table);
                ct3.Alignment = Element.ALIGN_LEFT;
                ct3.SetSimpleColumn(targetPosition.Left, 0, targetPosition.Right, targetPosition.Top, 0, 0);
                ct3.Go();

                foreach (PdfFormField f in checkboxes)
                
                    _pdfResultStamper.AddAnnotation(f, pageNum);
                
                checkboxes.Clear();

                if (itemIndex < (Items.Count - 1))
                
                    pageNum++;
                    _pdfResultStamper.InsertPage(pageNum, _pdfTemplateReader.GetPageSize(1));

                    table = new PdfPTable(availableSizes.Count + 1);
                    table.SetTotalWidth(columnWidth);
                    table.WidthPercentage = 100;
                    table.Rows.Add(new PdfPRow(this.GetTableHeaderRow(availableSizes)));
                
            
        
    
    private PdfPCell[] GetTableHeaderRow(List<string> AvailableSizes)
    
        PdfPCell[] sizeHeaders = new PdfPCell[AvailableSizes.Count + 1];
        Phrase devName = new Phrase("Device Name");
        PdfPCell deviceHeader = new PdfPCell(devName);
        deviceHeader.HorizontalAlignment = Element.ALIGN_CENTER;
        deviceHeader.BackgroundColor = BaseColor.GRAY;
        sizeHeaders[0] = deviceHeader;
        for (int x = 0; x < AvailableSizes.Count; x++)
        
            PdfPCell hCell = new PdfPCell(new Phrase(AvailableSizes[x]));
            hCell.HorizontalAlignment = Element.ALIGN_CENTER;
            hCell.BackgroundColor = BaseColor.GRAY;
            sizeHeaders[x + 1] = hCell;
        
        return sizeHeaders;
    
    public void MergePdfs(string filePath, List<MemoryStream> pdfStreams)
    
        //Create output stream            
        FileStream outStream = new FileStream(filePath, FileMode.Create);

        Document document = null;

        if (pdfStreams.Count > 0)
        
            try
            
                int PageCounter = 0;
                //Create Main reader
                PdfReader reader = new PdfReader(pdfStreams[0]);
                PageCounter = reader.NumberOfPages;//This is if we have multiple pages in the cover page, we need to adjust the offset.

                //rename fields in the PDF.  This is required because PDF's cannot have more than one field with the same name
                RenameFields(reader, PageCounter++);

                //Create Main Doc
                document = new Document(reader.GetPageSizeWithRotation(1));

                //Create main writer
                PdfCopy Writer = new PdfCopy(document, outStream);
                //PdfCopyFields Writer = new PdfCopyFields(outStream);

                //Open document for writing
                document.Open();
                ////Add pages
                Writer.AddDocument(reader);


                //For each additional pdf after first combine them into main document
                foreach (var PdfStream in pdfStreams.Skip(1))
                
                    PdfReader reader2 = new PdfReader(PdfStream);
                    //rename PDF fields
                    RenameFields(reader2, PageCounter++);
                    // Add content
                    Writer.AddDocument(reader);
                

                //Writer.AddJavaScript(PostProcessing.GetSuperscriptJavaScript());
                Writer.Close();
            
            catch (Exception ex)
            
                Console.WriteLine(ex.ToString());
            
            finally
            
                if (document != null)
                    document.Close();

                foreach (var Strm in pdfStreams)
                
                    try  if (null != Strm) Strm.Dispose(); 
                    catch  
                
                //pdfStamper.Close();
                outStream.Close();

            
        
    
    private void RenameFields(PdfReader reader, int PageNum)
    
        int tempPageNum = 1;
        //rename all fields
        foreach (string field in reader.AcroFields.Fields.Keys)
        
            if (((reader.AcroFields.GetFieldType(field) == 1) || (reader.AcroFields.GetFieldType(field) == 2)) && (field.StartsWith("InkSaver")))
            
                //This is a InkSaver button, set the name so its subclassed
                string classPath;
                if (reader.AcroFields.GetFieldType(field) == 2)
                
                    classPath = field.Substring(0, field.LastIndexOf("."));
                    if (field.StartsWith("InkSaver.chk"))
                    
                        int a = field.LastIndexOf(".");
                        string sub = field.Substring(a + 1, (field.Length - a - 1));
                        int pageNum = int.Parse(sub);
                        int realPageNum = pageNum + tempPageNum;//PostProcessing.Instance.CoverPageLength;
                        PageNum = realPageNum;
                    
                
                else
                
                    classPath = field.Substring(0, field.LastIndexOf("."));
                
                string newID = classPath + ".page" + PageNum.ToString();
                bool ret = reader.AcroFields.RenameField(field, newID);
            
            else
            
                reader.AcroFields.RenameField(field, field + "_" + PageNum.ToString());// field + Guid.NewGuid().ToString("N"));
            
        
    

public class ChildFieldEvent : IPdfPCellEvent

    protected PdfWriter writer;
    protected PdfFormField parent;
    protected string checkBoxName;

    internal ChildFieldEvent(PdfWriter writer, PdfFormField parent, string CheckBoxName)
    
        this.writer = writer;
        this.parent = parent;
        this.checkBoxName = CheckBoxName;
    

    public void CellLayout(PdfPCell cell, Rectangle rect, PdfContentByte[] cb)
    
        createCheckboxField(rect);
    
    private void createCheckboxField(Rectangle rect)
    
        RadioCheckField bt = new RadioCheckField(this.writer, rect, this.checkBoxName, "Yes");
        bt.CheckType = RadioCheckField.TYPE_SQUARE;
        bt.Checked = true;
        this.parent.AddKid(bt.CheckField);
    

internal class CustomCategory

    internal static List<string> AvailableSizes
    
        get
        
            List<string> retVal = new List<string>();
            retVal.Add("1");
            retVal.Add("2");
            retVal.Add("3");
            retVal.Add("4");

            return retVal;
        
    

    internal CustomCategory()
    
        CategorySizesInUse = new List<string>();
    
    internal List<string> CategorySizesInUse  get; set; 

internal class CustomObject

    internal string Title  get; set; 
    internal CustomCategory Category  get;set; 

【问题讨论】:

【参考方案1】:

请查看MergeForms 示例。您的示例对我来说太长了,但乍一看,我缺少以下行:

copy.setMergeFields();

顺便说一句,在MergeForms2中,在合并表单之前字段也被重命名了。

【讨论】:

我在 MergePdfs 中的 document.Open() 之后直接添加了 Writer.SetMergeFields()。现在,当我在方法末尾调用 Writer.Close() 时,我得到一个空引用异常。

以上是关于在 iTextSharp 5.4.5.0 中复制字段的主要内容,如果未能解决你的问题,请参考以下文章

样式未使用ITextSharp在PDF中实现[复制]

ITextSharp使用说明 (转)

iTextsharp PDF 文档属性

iTextsharp 将语言添加到 PDF 文档

使用多线程使用 iTextSharp 生成 Datamatrix 会导致崩溃

使用 iTextSharp 在系统中使用字体