如何在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原生机制自动生成目录:
- 生成内容时,为每个需加入目录的标题设置
SetDestination和SetOutline - 在标题页预留位置,最后调用
doc.Add(new TableOfContents()),iText7会自动计算页码并生成规范目录
内容的提问来源于stack exchange,提问作者Thompson
相关产品推荐
相关产品推荐

