C# 如何防止替换注释更改位置和文本大小?

C# 如何防止替换注释更改位置和文本大小?,c#,pdf,itext,C#,Pdf,Itext,我正在使用iTextSharp将打字机注释替换为具有相同内容和位置的文本框,但是一些生成的文本框最终位于不同的位置,具有不同的文本大小,尽管它们的hashMap中似乎有完全相同的数据 问题是,当我创建这些新注释时,然后在Adobe AcROAT XI中查看PDF时,新的文本框有以下问题: 它们已从原来的位置(靠近箭头)移动到页面上的较低位置 文本大小已更改 右键单击新文本框时,没有可用的属性 我怀疑这三个问题都是由于我如何创建新文本框的一个潜在问题造成的 当我在annot的hashMap中检

我正在使用iTextSharp将打字机注释替换为具有相同内容和位置的文本框,但是一些生成的文本框最终位于不同的位置,具有不同的文本大小,尽管它们的
hashMap
中似乎有完全相同的数据

<席>问题是,当我创建这些新注释时,然后在Adobe AcROAT XI中查看PDF时,新的文本框有以下问题:

  • 它们已从原来的位置(靠近箭头)移动到页面上的较低位置

  • 文本大小已更改

  • 右键单击新文本框时,没有可用的属性

我怀疑这三个问题都是由于我如何创建新文本框的一个潜在问题造成的

当我在
annot
hashMap
中检查
/Rect
键时,它与原始的
freeTextAnnot
具有相同的矩形坐标顺序,因此我不理解为什么一些注释最终会被替换

下面是我用现有打字机注释数据创建新文本框的代码。请注意,您需要将
inputPath
outputPath
设置为PDF的实际位置及其目标路径:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using iTextSharp;
using iTextSharp.text.pdf;
using System.IO;

namespace PDFConverterTester
{
    public class PdfModifierTester
    {
        public void testWithPaths()
        {
            //set to location of test PDF
            string inputPath = @"C:\InputPath\Before Conversion Dummy.pdf";
            //set to destination of new PDF
            string outputPath = @"C:\OutputPath\Before Conversion Dummy.pdf";
            test(inputPath, outputPath);
        }

        public void test(string inputPath, string outputPath)
        {
            PdfReader pdfReader = new PdfReader(inputPath);
            PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(outputPath, FileMode.Create));
            //get the PdfDictionary of the 1st page
            PdfDictionary pageDict = pdfReader.GetPageN(1);
            //get annotation array
            PdfArray annotArray = pageDict.GetAsArray(PdfName.ANNOTS);
            //iterate through annotation array
            int size = annotArray.Size;
            for (int i = size - 1; i >= 0; i--)
            {
                PdfDictionary dict = annotArray.GetAsDict(i);
                PdfName ITName = dict.GetAsName(new PdfName("IT"));
                if (ITName != null)
                {
                    if (ITName.Equals(new PdfName("FreeTextTypewriter")))
                    {
                        PdfAnnotation annot = copyToNewAnnotation_SSCCE(dict,pdfStamper);
                        pdfStamper.AddAnnotation(annot, 1);
                        annotArray.Remove(i);
                    }
                }
            }
            pdfStamper.Close();
            pdfReader.Close();
        }

        private PdfAnnotation copyToNewAnnotation_SSCCE(PdfDictionary freeTextAnnot,PdfStamper pdfStamper)
        {

            //need Rectangle for CreateFreeText()
            iTextSharp.text.Rectangle rect;
            PdfArray rectArray = freeTextAnnot.GetAsArray(PdfName.RECT);
            if (rectArray == null)
            {
                rect = null;
            }
            else
            {
                rect = new iTextSharp.text.Rectangle(getFloat(rectArray, 0),
                                                                          getFloat(rectArray, 1),
                                                                          getFloat(rectArray, 2),
                                                                          getFloat(rectArray, 3));
            }
            //create new annotation
            PdfContentByte pcb = new PdfContentByte(pdfStamper.Writer);
            PdfAnnotation annot = PdfAnnotation.CreateFreeText(pdfStamper.Writer, rect, "", pcb);
            //make array of all possible PdfName keys in dictionary for freeTextAnnot
            string pdfNames = "AP,BS,C,CA,CL,CONTENTS,CREATIONDATE,DA,DS,F,IT,LE,M,NM,P,POPUP,Q,RC,RD,ROTATE,SUBJ,SUBTYPE,T,TYPE";
            string[] pdfNameArray = pdfNames.Split(',');
            //iterate through key array copying key-value pairs to new annotation
            foreach (string pdfName in pdfNameArray)
            {
                //get value for this PdfName
                PdfName key = new PdfName(pdfName);
                PdfObject obj = freeTextAnnot.Get(key);
                //if returned value is null, maybe key is case-sensitive
                if (obj == null)
                {
                    //try with first letter only capitalized, e.g. "Contents" instead of "CONTENTS"
                    string firstCappdfName = char.ToUpper(pdfName[0]) + pdfName.Substring(1);
                    key = new PdfName(firstCappdfName);
                    obj = freeTextAnnot.Get(key);
                }
                //set key-value pair in new annotation
                annot.Put(key, obj);
            }
            //change annotation type to Textbox
            annot.Put(PdfName.SUBTYPE, PdfName.FREETEXT);
            annot.Put(new PdfName("Subj"), new PdfString("Textbox"));

            //set a default blank border
            annot.Put(PdfName.BORDER, new PdfBorderArray(0, 0, 0));

            return annot;


        }

        private float getFloat(PdfArray arr, int index)
        {
            return float.Parse(arr[index].ToString());
        }
    }
}
编辑:似乎部分问题可能出在调用
pdfStamper.AddAnnotation(annot,1)
中,因为
annot
键的
/Rect
值在此调用后发生更改。例如:

AddAnnotation()之前调用:

{[2401, 408.56, 2445.64, 693]}
通话后:

{[1899, 2445.64, 2183.44, 2401]}
因此,可能是来自
PdfStamper.AddAnnotation()
()第1463-1493行的以下代码造成的,我目前正在调查这种可能性:

if (!annot.IsUsed()) {
                        PdfRectangle rect = (PdfRectangle)annot.Get(PdfName.RECT);
                        if (rect != null && (rect.Left != 0 || rect.Right != 0 || rect.Top != 0 || rect.Bottom != 0)) {
                            int rotation = reader.GetPageRotation(pageN);
                            Rectangle pageSize = reader.GetPageSizeWithRotation(pageN);
                            switch (rotation) {
                                case 90:
                                    annot.Put(PdfName.RECT, new PdfRectangle(
                                        pageSize.Top - rect.Top,
                                        rect.Right,
                                        pageSize.Top - rect.Bottom,
                                        rect.Left));
                                    break;
                                case 180:
                                    annot.Put(PdfName.RECT, new PdfRectangle(
                                        pageSize.Right - rect.Left,
                                        pageSize.Top - rect.Bottom,
                                        pageSize.Right - rect.Right,
                                        pageSize.Top - rect.Top));
                                    break;
                                case 270:
                                    annot.Put(PdfName.RECT, new PdfRectangle(
                                        rect.Bottom,
                                        pageSize.Right - rect.Left,
                                        rect.Top,
                                        pageSize.Right - rect.Right));
                                    break;
                            }
                        }
                    }
                }
原因 您自己已经找到注释位置和尺寸变化的原因:

似乎部分问题可能是在调用
pdfStamper.AddAnnotation(annot,1)
时出现的,因为
annot
键的
/Rect
值在此调用后发生更改

。。。由
PdfStamper.AddAnnotation()
(链接到源代码)第1463-1493行的代码负责

事实上,如果页面旋转,该代码会更改注释矩形

这背后的基本原理是,对于旋转页面,iText试图减轻将旋转和平移添加到页面内容的负担,以绘制竖直文本,并将坐标系原点置于用户肩部页面的左下角,这样用户根本不必处理页面旋转。因此,它对注释也会这样做

对于页面内容,有一个
PdfStamper
属性
RotateContents
默认为
true
,如果用户明确不希望进行此旋转和翻译,则可以关闭此属性。不幸的是,注释没有类似的属性,它们的位置和大小总是得到“更正”,即旋转和平移

变通办法 由于页面旋转是iText旋转和平移矩形的触发器,因此只需在操作注释之前移除页面旋转,然后再添加:

PdfDictionary pageDict = pdfReader.GetPageN(1);
// hide the page rotation
PdfNumber rotation = pageDict.GetAsNumber(PdfName.ROTATE);
pageDict.Remove(PdfName.ROTATE);
//get annotation array
PdfArray annotArray = pageDict.GetAsArray(PdfName.ANNOTS);
//iterate through annotation array
int size = annotArray.Size;
for (int i = size - 1; i >= 0; i--)
{
    ...
}
// add page rotation again if required
if (rotation != null)
    pageDict.Put(PdfName.ROTATE, rotation);
pdfStamper.Close();
添加此项后,注释将保持不变

缺少的属性 你还观察到:

右键单击新文本框时,没有可用的属性

这是因为您没有更改intent(IT)条目,因此它们仍然包含FreeTextTypewriter,因此Adobe Reader不确定这是什么类型的对象,因此不提供属性对话框。如果您还更改了目的:

//change annotation type to Textbox
annot.Put(PdfName.SUBTYPE, PdfName.FREETEXT);
annot.Put(new PdfName("IT"), PdfName.FREETEXT); // <======
annot.Put(new PdfName("Subj"), new PdfString("Textbox"));
另一种方法 替换原始注释而不是简单地编辑注释有什么具体原因吗?例如:

public void AlternativeReplaceFreetextByTextbox(string InputPath, string OutputPath)
{
    PdfName IT = new PdfName("IT");
    PdfName FREETEXTTYPEWRITER = new PdfName("FreeTextTypewriter");
    using (PdfReader Reader = new PdfReader(InputPath))
    {
        PdfDictionary Page = Reader.GetPageN(1);
        PdfArray Annotations = Page.GetAsArray(PdfName.ANNOTS);
        foreach (PdfObject Object in Annotations)
        {
            PdfDictionary Annotation = (PdfDictionary)PdfReader.GetPdfObject(Object);
            PdfName Intent = Annotation.GetAsName(IT);
            if (FREETEXTTYPEWRITER.Equals(Intent))
            {
                // change annotation type to Textbox
                Annotation.Put(IT, PdfName.FREETEXT);
            }
        }

        using (PdfStamper Stamper = new PdfStamper(Reader, new FileStream(OutputPath, FileMode.Create)))
        { }
    }

}

和往常一样,找到复杂问题的解决方案是件好事。类似于关于删除页面旋转和固定区域设置的注释。
public void AlternativeReplaceFreetextByTextbox(string InputPath, string OutputPath)
{
    PdfName IT = new PdfName("IT");
    PdfName FREETEXTTYPEWRITER = new PdfName("FreeTextTypewriter");
    using (PdfReader Reader = new PdfReader(InputPath))
    {
        PdfDictionary Page = Reader.GetPageN(1);
        PdfArray Annotations = Page.GetAsArray(PdfName.ANNOTS);
        foreach (PdfObject Object in Annotations)
        {
            PdfDictionary Annotation = (PdfDictionary)PdfReader.GetPdfObject(Object);
            PdfName Intent = Annotation.GetAsName(IT);
            if (FREETEXTTYPEWRITER.Equals(Intent))
            {
                // change annotation type to Textbox
                Annotation.Put(IT, PdfName.FREETEXT);
            }
        }

        using (PdfStamper Stamper = new PdfStamper(Reader, new FileStream(OutputPath, FileMode.Create)))
        { }
    }

}