【问题标题】:How to prevent replacement annotation from changing position and text size?如何防止替换注释改变位置和文本大小?
【发布时间】:2016-11-30 00:40:29
【问题描述】:

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

问题是当我在this sample PDF上创建这些新注释,然后在Adobe Acrobat XI中查看PDF时,新的Textboxes有以下问题:

  • 它们已从原始位置(与箭头相邻)移动到页面下方

  • 文字大小改变了

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

我怀疑所有 3 个问题都是由于我如何创建新文本框的一个潜在问题。

当我检查hashMap 中的/Rect 键为annot 时,它具有与原始freeTextAnnot 相同顺序的矩形坐标,所以我不明白为什么某些注释结束向上移位。

这是我使用现有打字机注释数据创建新文本框的代码。请注意,您需要将 inputPathoutputPath 设置为 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() (link to source) 的以下代码第 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;
                            }
                        }
                    }
                }

【问题讨论】:

    标签: c# pdf itext


    【解决方案1】:

    原因

    你自己找到了注释位置和尺寸变化的原因:

    似乎部分问题可能出在对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();
    

    添加后,注释将保持不变。

    缺少的属性

    您还观察到:

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

    这是因为您没有更改意图 (IT) 条目,所以它们仍然包含 FreeTextTypewriter,因此 Adob​​e 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"));
    

    您将看到“属性”对话框。

    顺便说一句

    您的方法getFloat 首先对我造成了坐标系最奇怪的变化,因为我的语言环境不使用点作为小数分隔符。

    我将其更改为这个以使其与语言环境无关:

    private float getFloat(PdfArray arr, int index)
    {
        return arr.GetAsNumber(index).FloatValue;
    }
    

    另一种方法

    您替换原始注释而不是简单地编辑它是否有特定原因?例如:

    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)))
            { }
        }
    
    }
    

    【讨论】:

    • 一如既往,为复杂问题找到解决方案做得很好。就像删除页面旋转和修复语言环境的注释一样。
    猜你喜欢
    • 1970-01-01
    • 2019-07-24
    • 2015-12-05
    • 2016-05-25
    • 1970-01-01
    • 2018-08-28
    • 2015-06-25
    • 1970-01-01
    • 2021-02-15
    相关资源
    最近更新 更多