【问题标题】:In C#, while generating a PDF, how to get page number in footer in the format "Page 1 of n" or "Page 1/n"在 C# 中,在生成 PDF 时,如何以“第 1 页,共 n 页”或“第 1/n 页”格式获取页脚中的页码
【发布时间】:2020-09-20 15:38:07
【问题描述】:

在将某些数据发布为 PDF 的 C# 代码中,我正在寻找一种将页码放在页脚中的方法(左、中或右索引)。

格式应如下所示:“第 1 页,共 n 页”,其中 n 表示总页数。

PdfWriter writer = PdfWriter.GetInstance(doc, ms);
writer.CloseStream = false;

doc.Open();
//Main pdf publishing code follows 

doc.Close();

【问题讨论】:

  • 请在itext知识库中搜索示例代码。您会发现两个选项,一个基于页面事件的单通道选项和一个基于将PdfStamper 应用于生成的 pdf 的两通道版本。
  • @mkl 我是编码新手。我一直难以理解两步法,它从哪里开始,如何在我的案例中应用它。是否必须定义和调用方法。我发现的代码中没有完整的端到端示例。你能帮忙吗?

标签: c# pdf github itext page-numbering


【解决方案1】:

这感觉就像堆栈溢出的许多问题和答案的重复,但这些问题的大多数答案现在都不是很有帮助,因为它们链接到无法再找到的示例,例如因为iText 代码库从 sourceforge 迁移到 github 或由于 itext 网站的重新设计。此外,OP 在评论中表示他确实知道一种方法,只是不知道如何将其应用于他的案例。

两种主要方法

首先,有两种主要方法可以将“Page x of y”页眉或页脚添加到您使用 iText 创建的文档的页面中:

  • (一次性方法)您可以使用页面事件(特别是OnEndPage 方法)在此页面完成时和下一页开始之前将页眉/页脚/边距材料添加到每个页面。李>
  • (两遍方法)或者您可以在第一遍中创建没有这些页眉或页脚的 PDF,然后阅读该 PDF 并在第二遍中标记页脚/页眉/matgin 材料。

通常人们更喜欢页眉和页脚材料的页面事件,因为这样就不需要从第一遍临时存储 PDF,而是可以在创建过程中将其流式传输到目标。

“Page x of y”页脚/页眉是特殊的,因为在所有内容都写完之前,人们不知道最终的页数。因此,对于页面事件方法,必须使用一种技巧并将对画布的引用添加到应出现总页码的每个页面。当所有常规内容都添加到文档中时,最后将总页码写入其中。不幸的是,人们不知道该模板究竟需要多少空间,因此生成的页眉/页脚中的格式最终可能看起来有点尴尬。因此,这里可能首选两次通过的方法。

如何使用 iText 5.x 在两次传递中向 PDF 添加“Page x of y”标题在 Bruno Lowagie 撰写的“iText in Action - 2nd Edition”一书的第 6 章中的示例 TwoPasses 中进行了描述。 kuujinbo 已将原始 Java 示例移植到 C#。这里是关键代码:

PdfReader reader = new PdfReader(firstPass);
using (MemoryStream ms2 = new MemoryStream()) {
    // Create a stamper
    using (PdfStamper stamper = new PdfStamper(reader, ms2)) {
        // Loop over the pages and add a header to each page
        int n = reader.NumberOfPages;
        for (int i = 1; i <= n; i++) {
            GetHeaderTable(i, n).WriteSelectedRows(0, -1, 34, 803, stamper.GetOverContent(i));
        }
    }
    // write content of ms2 somewhere, e.g. as ms2.ToArray()
}

public static PdfPTable GetHeaderTable(int x, int y) {
    PdfPTable table = new PdfPTable(2);
    table.TotalWidth = 527;
    table.LockedWidth = true;
    table.DefaultCell.FixedHeight = 20;
    table.DefaultCell.Border = Rectangle.BOTTOM_BORDER;
    table.AddCell("FOOBAR FILMFESTIVAL");
    table.DefaultCell.HorizontalAlignment = Element.ALIGN_RIGHT;
    table.AddCell(string.Format("Page {0} of {1}", x, y));
    return table;
}    

github/kuujinbo.info

适用于您的案例

OP 在评论中写道

我一直难以理解两步法,它从哪里开始,如何在我的案例中应用它

好的,你描述你的情况是这样的:

iTextSharp.text.Document doc = new iTextSharp.text.Document(PageSize.A4.Rotate(), 50f, 50f, 50f, 50f);
{
    try
    {
        //Main pdf publishing code follows 
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error! try again.", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    finally
    {
        doc.Close();
    }
}

首先我建议你把它改成这样:

try
{
    using (iTextSharp.text.Document doc = new iTextSharp.text.Document(PageSize.A4.Rotate(), 50f, 50f, 50f, 50f))
    {
        //Main pdf publishing code follows
    }
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message, "Error! try again.", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

因为您的原始方法错过了关闭文档对象期间引发的异常! using (Document doc = ...) {...} 结构替换了 Document doc = ...; try {...} finally {doc.Close();} 结构。

现在您的 //Main pdf publishing code follows 特别包含 PdfWriter 实例化,类似于

PdfWriter writer = PdfWriter.GetInstance(doc, SOME_STREAM);

对于两遍方法,您必须改为将 PdfWriter 定位到临时流。然后,您将上面引用的两次密码应用于其中的内容。只有这样的结果才能最终转到您的SOME_STREAM

大家一起:

try
{
    using (MemoryStream ms = new MemoryStream())
    {
        float leftRightMargin = 50f;
        using (iTextSharp.text.Document doc = new iTextSharp.text.Document(PageSize.A4.Rotate(), leftRightMargin, leftRightMargin, 50f, 50f))
        {
            PdfWriter writer = PdfWriter.GetInstance(doc, ms);
            //Main pdf publishing code (except PdfWriter instantiation) follows
        }

        using (PdfReader reader = new PdfReader(ms.ToArray()))
        using (PdfStamper stamper = new PdfStamper(reader, SOME_STREAM))
        {
            int n = reader.NumberOfPages;
            for (int i = 1; i <= n; i++)
            {
                Rectangle cropBox = reader.GetCropBox(i);
                for (int rotation = reader.GetPageRotation(i); rotation > 0; rotation -= 90)
                    cropBox = cropBox.Rotate();
                GetHeaderTable(i, n, cropBox.Width - 2 * leftRightMargin).WriteSelectedRows(0, -1, leftRightMargin, cropBox.GetTop(10), stamper.GetOverContent(i));
            }
        }
    }
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message, "Error! try again.", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

public static PdfPTable GetHeaderTable(int x, int y, float width)
{
    PdfPTable table = new PdfPTable(2);
    table.TotalWidth = width;
    table.LockedWidth = true;
    table.DefaultCell.FixedHeight = 20;
    table.DefaultCell.Border = Rectangle.BOTTOM_BORDER;
    table.AddCell("Header test");
    table.DefaultCell.HorizontalAlignment = Element.ALIGN_RIGHT;
    table.AddCell(string.Format("Page {0} of {1}", x, y));
    return table;
}

【讨论】:

  • 感谢端到端的详细回复。解决方案返回一个错误,我也很少有查询。错误是“无法访问关闭的流”(我已经更新了我的问题中的新代码)。我的第一个问题是,在更改后的代码中,我们使用 doc.open() 打开了文档,但我们没有关闭它,可以吗?我的第二个查询是我必须将 GetHeaderTable 中的 defaultcellborder 从“Rectangle”更改为“iTextSharp.text.Rectangle”,可以吗?
  • 至于报错,调试返回“Your application is in Break Mode”。
  • 所以这个错误我已经能够通过将 writer.closestream 设置为 false 来解决。但是现在出现了一个不同的错误,“未找到 PDF 标头签名”。有什么想法吗?
  • 啊,对不起,我的错。请将new PdfReader(ms) 替换为new PdfReader(ms.ToArray())。我会相应地编辑我的答案。
  • 所以代码现在正在运行,但是生成的pdf中仍然缺少所需的页码。我已经为当前版本编辑了问题中的代码。请看一看。
猜你喜欢
  • 1970-01-01
  • 2018-02-27
  • 2015-05-21
  • 2015-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 1970-01-01
相关资源
最近更新 更多