You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在iText7(C#)的标题页Cell中添加目录?

问题:iText7生成PDF时标题页目录Cell填充后显示为空

我需要在PDF标题页的Cell中插入目录(TOC),由于必须先生成内容才能确定目录数据,所以实现流程是:

  • 在CreateTitlePage方法中创建包含空白Cell(tocArea)的标题页
  • 通过CreateContentPages方法生成内容并填充toc列表
  • 最后调用FillTOC方法向tocArea添加目录内容

但PDF渲染后该Cell区域显示为空,且无任何报错信息,相关代码如下:

namespace ProductTest
{
    internal class ProductDocument
    {
        // 省略类变量

        List<KeyValuePair<string, KeyValuePair<string, int>>> toc = new List<KeyValuePair<string, KeyValuePair<string, int>>>();

        public void CreatePdf()
        {
            // 通用设置
            string filename = "ProductTest.pdf";

            PdfDocument bgPdf = new PdfDocument(new PdfReader(templateLocation));
            PdfPage bgPage = bgPdf.GetPage(1);

            PageSize pageSize = PageSize.A4.Rotate();

            PdfDocument pdf = new PdfDocument(new PdfWriter(filename));
            pdf.AddEventHandler(PdfDocumentEvent.END_PAGE, new HeaderFooterEventHandler(pdf));

            Document doc = new Document(pdf, pageSize, false);
            DocumentRenderer renderer = new DocumentRenderer(doc, false);
            doc.SetRenderer(renderer);

            doc.SetMargins(40, 6, 30, 6);

            Cell tocArea = new Cell().SetBorder(null);

            CreateTitlePage(doc, pageSize, bgPage, tocArea);

            CreateContentPages(doc, pageSize);

            FillTOC(doc, tocArea);

            bgPdf.Close();
            doc.Close();

            Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true });
        }

        private void CreateContentPages(Document doc, PageSize pageSize)
        {
           PdfDocument pdf = doc.GetPdfDocument();
           PdfPage page = pdf.AddNewPage(pageSize);
           
           // 设置分栏
           float horizontalOffset = 20;
           float verticalOffset = 60;
           float gap = 50;
           float columnWidth = (pageSize.GetWidth() - 2 * horizontalOffset - gap) / 2;
           float columnHeight = pageSize.GetHeight() - 2 * verticalOffset;

           Rectangle[] columns = new Rectangle[]
           {
               new Rectangle(horizontalOffset, verticalOffset, columnWidth, columnHeight),
               new Rectangle(horizontalOffset + columnWidth + gap, verticalOffset, columnWidth, columnHeight)
           };

           doc.SetRenderer(new ColumnDocumentRenderer(doc, false, columns));
           doc.Add(new AreaBreak(AreaBreakType.LAST_PAGE));

           // 此处生成内容并填充toc列表,代码省略
        }

        private void CreateTitlePage(Document doc, PageSize pageSize, PdfPage bgPage, Cell tocArea)
        {
            PdfDocument pdf = doc.GetPdfDocument();
            PdfPage page = pdf.AddNewPage(pageSize);
            Rectangle orig = bgPage.GetPageSizeWithRotation();
            doc.SetRenderer(new DocumentRenderer(doc));
            doc.Add(new AreaBreak(AreaBreakType.LAST_PAGE));

            // 复制背景模板
            PdfCanvas canvas = new PdfCanvas(page);
            AffineTransform transformationMatrix = AffineTransform.GetScaleInstance(page.GetPageSize().GetWidth() / orig.GetWidth(), page.GetPageSize().GetHeight() / orig.GetHeight());
            canvas.ConcatMatrix(transformationMatrix);
            PdfFormXObject pageCopy = bgPage.CopyAsFormXObject(pdf);
            canvas.AddXObjectAt(pageCopy, 0, 0);

            // 标题页布局
            float infBoxWidth = 184;
            float titleContentWidth = page.GetPageSize().GetWidth() - doc.GetLeftMargin() - doc.GetRightMargin() - infBoxWidth;

            Table layout = new Table(new float[] { titleContentWidth, infBoxWidth });
            layout.SetWidth(UnitValue.CreatePercentValue(100))
                .AddCell(new Cell())
                .AddCell(new Cell())
                .SetMargins(24, 0, 0, 0);

            // 左列内容,代码省略
            string headingText = "Content / Appendix";
            outline = CreateOutline(outline, pdf, headingText, headingText);
            leftColumn.Add(new Paragraph(headingText).AddStyle(heading2).SetDestination(headingText));

            // 在此添加占位Cell
            Table table = new Table(1).SetWidth(UnitValue.CreatePercentValue(100)).SetMarginLeft(20).AddCell(tocArea);
            leftColumn.Add(table);

            // 右列信息框,代码省略

            doc.Add(layout);
        }

        // 尝试填充目录,无报错但Cell显示空白
        private void FillTOC(Document doc, Cell tocArea)
        {
            // 创建目录
            int counter = 0;
            List<TabStop> tabStops = new List<TabStop>();
            DottedLine dottedLine = new DottedLine(1, 3);
            dottedLine.SetColor(ColorConstants.BLUE);
            tabStops.Add(new TabStop(600, TabAlignment.RIGHT, dottedLine));
            foreach (KeyValuePair<string, KeyValuePair<string, int>> entry in toc)
            {
                KeyValuePair<string, int> text = entry.Value;
                Paragraph paragraph = new Paragraph()
                    .SetFont(arial)
                    .SetFontSize(9)
                    .SetFontColor(ColorConstants.BLUE)
                    .SetTextAlignment(TextAlignment.JUSTIFIED)
                    .AddTabStops(tabStops)
                    .Add($"{++counter:d2} " + text.Key)
                    .Add(new Tab())
                    .Add(text.Value.ToString())
                    .SetAction(PdfAction.CreateGoTo(entry.Key));
                tocArea.Add(paragraph);
            }
            
        }

        // 向文档添加书签,代码省略
        private PdfOutline CreateOutline(PdfOutline outline, PdfDocument pdf, String title, String name)
        {
            // 代码省略
        }

        // 向页面添加页眉和页码,代码省略
        private class HeaderFooterEventHandler : IEventHandler
        {
            // 代码省略
        }
    }
}

解决方案

问题核心是iText7的元素渲染时机:当调用doc.Add(layout)时,tocArea作为layout的一部分已经被渲染到PDF页面中,后续对tocArea添加内容的操作不会触发重新渲染,因此最终显示空白。

针对现有代码的快速修复

放弃修改已渲染的tocArea,直接在标题页的对应位置用PdfCanvas绘制目录内容:

private void FillTOC(Document doc, float x, float y, float width)
{
    PdfDocument pdf = doc.GetPdfDocument();
    PdfPage titlePage = pdf.GetPage(1); // 假设标题页是第1页
    PdfCanvas canvas = new PdfCanvas(titlePage);
    
    int counter = 0;
    float currentY = y;
    float lineHeight = 12; // 行高
    
    foreach (KeyValuePair<string, KeyValuePair<string, int>> entry in toc)
    {
        KeyValuePair<string, int> text = entry.Value;
        string content = $"{++counter:d2} {text.Key}";
        string pageNum = text.Value.ToString();
        
        // 绘制标题文本
        canvas.BeginText()
              .SetFontAndSize(arial.GetFontProgram(), 9)
              .SetColor(ColorConstants.BLUE)
              .MoveText(x, currentY)
              .ShowText(content)
              .EndText();
        
        // 绘制页码
        float pageNumWidth = arial.GetFontProgram().GetWidth(9, pageNum) / 1000 * 9;
        canvas.BeginText()
              .SetFontAndSize(arial.GetFontProgram(), 9)
              .SetColor(ColorConstants.BLUE)
              .MoveText(x + width - pageNumWidth, currentY)
              .ShowText(pageNum)
              .EndText();
        
        // 绘制虚线连接
        canvas.SetStrokeColor(ColorConstants.BLUE)
              .SetLineWidth(1)
              .SetLineDash(3, 3)
              .MoveTo(x + arial.GetFontProgram().GetWidth(9, content)/1000*9 + 5, currentY + 4)
              .LineTo(x + width - pageNumWidth - 5, currentY + 4)
              .Stroke();
        
        currentY -= lineHeight;
    }
}

调用时传入标题页目录区域的实际坐标和宽度(需根据你的布局调整数值):

// 替换原FillTOC(doc, tocArea);调用
FillTOC(doc, 64, 500, 500);

推荐方案:使用iText7内置TOC功能

无需手动维护toc列表,利用iText7原生机制自动生成目录:

  1. 生成内容时,为每个需加入目录的标题设置SetDestination和SetOutline
  2. 在标题页预留位置,最后调用doc.Add(new TableOfContents()),iText7会自动计算页码并生成规范目录

内容的提问来源于stack exchange,提问作者Thompson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 15:35:39