iTextSharp - 在合并 PDF 中使用 PDFAction.GotoLocalPage

Nic*_*eve 1 pdf merge itext tableofcontents

我编写了一些代码,将多个 PDF 合并为一个 PDF,然后从 MemoryStream 中显示该 PDF。这很好用。我需要做的是将目录添加到文件末尾,并提供指向每个 PDF 开头的链接。我计划使用 GotoLocalPage 操作来执行此操作,该操作具有页码选项,但它似乎不起作用。如果我将下面代码的操作更改为 PDFAction.FIRSTPAGE 等 Presset 操作之一,它就可以正常工作。这是否不起作用,因为我使用 PDFCopy 对象作为 GotoLocalPage 的 writer 参数?

Document mergedDoc = new Document();
MemoryStream ms = new MemoryStream();
PdfCopy copy = new PdfCopy(mergedDoc, ms);
mergedDoc.Open();

MemoryStream tocMS = new MemoryStream();
Document tocDoc = null;
PdfWriter tocWriter = null;

for (int i = 0; i < filesToMerge.Length; i++)
{
     string filename = filesToMerge[i];

     PdfReader reader = new PdfReader(filename);
     copy.AddDocument(reader);

     // Initialise TOC document based off first file
     if (i == 0)
     {
          tocDoc = new Document(reader.GetPageSizeWithRotation(1));
          tocWriter = PdfWriter.GetInstance(tocDoc, tocMS);
          tocDoc.Open();
     }

     // Create link for TOC, added random number of 3 for now
     Chunk link = new Chunk(filename);
     PdfAction action = PdfAction.GotoLocalPage(3, new PdfDestination(PdfDestination.FIT), copy);
     link.SetAction(action);
     tocDoc.Add(new Paragraph(link));
}

// Add TOC to end of merged PDF
tocDoc.Close();
PdfReader tocReader = new PdfReader(tocMS.ToArray());
copy.AddDocument(tocReader);

copy.Close();

displayPDF(ms.ToArray());
Run Code Online (Sandbox Code Playgroud)

我想另一种选择是链接到命名元素(而不是页码),但我不知道如何在添加到合并文档之前将“不可见”元素添加到每个文件的开头?

Chr*_*aas 5

我只需要两张通行证就可以了。在第一遍中,按原样进行合并,但还要记录它应链接到的文件名和页码。在第二遍中,使用 a PdfStamper,这将使您可以访问 a ColumnText,您可以使用像 in 一样的一般抽象Paragraph。下面是一个示例,展示了这一点:

由于我没有您的文档,因此下面的代码创建 10 个文档,每个文档的页数是随机的,仅用于测试目的。(显然您不需要执行这部分操作。)它还会创建一个简单的字典,以假文件名作为键,将 PDF 中的原始字节作为值。您有一个真正的文件集合可供使用,但您应该能够调整该部分。

//Create a bunch of files, nothing special here
//files will be a dictionary of names and the raw PDF bytes
Dictionary<string, byte[]> Files = new Dictionary<string, byte[]>();
var r = new Random();
for (var i = 1; i <= 10; i++) {
    using (var ms = new MemoryStream()) {
        using (var doc = new Document()) {
            using (var writer = PdfWriter.GetInstance(doc, ms)) {
                doc.Open();

                //Create a random number of pages
                for (var j = 1; j <= r.Next(1, 5); j++) {
                    doc.NewPage();
                    doc.Add(new Paragraph(String.Format("Hello from document {0} page {1}", i, j)));
                }
                doc.Close();
            }
        }
        Files.Add("File " + i.ToString(), ms.ToArray());
    }
}
Run Code Online (Sandbox Code Playgroud)

下一个块将合并 PDF。这与您的代码基本相同,只是我不是在这里编写目录,而是跟踪我将来想要编写的内容。在我使用的地方,file.value您将使用完整的文件路径,在我使用的file.key地方,您将使用文件的名称。

//Dictionary of file names (for display purposes) and their page numbers
var pages = new Dictionary<string, int>();

//PDFs start at page 1
var lastPageNumber = 1;

//Will hold the final merged PDF bytes
byte[] mergedBytes;

//Most everything else below is standard
using (var ms = new MemoryStream()) {
    using (var document = new Document()) {
        using (var writer = new PdfCopy(document, ms)) {
            document.Open();

            foreach (var file in Files) {

                //Add the current page at the previous page number
                pages.Add(file.Key, lastPageNumber);

                using (var reader = new PdfReader(file.Value)) {
                    writer.AddDocument(reader);

                    //Increment our current page index
                    lastPageNumber += reader.NumberOfPages;
                }
            }
        }
    }
    mergedBytes = ms.ToArray();
}
Run Code Online (Sandbox Code Playgroud)

最后一个块实际上写入了目录。如果我们使用 aPdfStamper我们可以创建一个ColumnText允许我们使用Paragraphs

//Will hold the final PDF
byte[] finalBytes;
using (var ms = new MemoryStream()) {
    using (var reader = new PdfReader(mergedBytes)) {
        using (var stamper = new PdfStamper(reader, ms)) {

            //The page number to insert our TOC into
            var tocPageNum = reader.NumberOfPages + 1;

            //Arbitrarily pick one page to use as the size of the PDF
            //Additional logic could be added or this could just be set to something like PageSize.LETTER
            var tocPageSize = reader.GetPageSize(1);

            //Arbitrary margin for the page
            var tocMargin = 20;

            //Create our new page
            stamper.InsertPage(tocPageNum, tocPageSize);

            //Create a ColumnText object so that we can use abstractions like Paragraph
            var ct = new ColumnText(stamper.GetOverContent(tocPageNum));

            //Set the working area
            ct.SetSimpleColumn(tocPageSize.GetLeft(tocMargin), tocPageSize.GetBottom(tocMargin), tocPageSize.GetRight(tocMargin), tocPageSize.GetTop(tocMargin));

            //Loop through each page
            foreach (var page in pages) {
                var link = new Chunk(page.Key);
                var action = PdfAction.GotoLocalPage(page.Value, new PdfDestination(PdfDestination.FIT), stamper.Writer);
                link.SetAction(action);
                ct.AddElement(new Paragraph(link));
            }

            ct.Go();
        }
    }
    finalBytes = ms.ToArray();
}
Run Code Online (Sandbox Code Playgroud)