TocFromOutlines.cs
 // // This code is part of Document Solutions for PDF demos. // Copyright (c) MESCIUS inc. All rights reserved. // using System; using System.IO; using System.Drawing; using GrapeCity.Documents.Pdf; using GrapeCity.Documents.Text; using GrapeCity.Documents.Pdf.Annotations; namespace DsPdfWeb.Demos { // This example shows how to use the Outlines collection of an existing PDF // to build a table of contents and insert that TOC at the top of the document. public class TocFromOutlines { public int CreatePDF(Stream stream) { // TOC layout setup: var margin = 36; var levelOffset = 12; // Horizontal space allocated for the page numbers: var pageSpace = 24; // Vertical gap between TOC entries: var gap = 4; var doc = new GcPdfDocument(); using var fs = File.OpenRead(Path.Combine("Resources", "PDFs", "guide-wetland-birds.pdf")); doc.Load(fs); // Add sacrificial page and create text layout: var page = doc.Pages.Add(); var tl = page.Graphics.CreateTextLayout(); InitLayout(0); // Measure a dot: var dotW = page.Graphics.MeasureString(new string('.', 12), tl.DefaultFormat).Width / 12; // Dry run to count the number of pages in the TOC: float top = margin; int tocPages = 0; bool drawCaption = true; MakeToc(doc.Outlines, 0, true); // Live run to insert the TOC in the doc: doc.Pages.RemoveAt(doc.Pages.Count - 1); page = doc.Pages.Insert(0); InitLayout(0); top = margin; drawCaption = true; MakeToc(doc.Outlines, 0, false); // Done: doc.Save(stream); return doc.Pages.Count; void InitLayout(int level) { tl.MarginTop = margin; tl.MarginBottom = margin; tl.MarginLeft = margin + levelOffset * level; tl.MarginRight = margin + pageSpace; tl.MaxWidth = page.Size.Width; tl.MaxHeight = page.Size.Height; } (int pageIdx, Destination newDest) PageIdxFromDest(DestinationBase dest) { IDestination dd; if (dest is DestinationRef df) doc.NamedDestinations.TryGetValue(df.Name, out dd); else dd = dest as Destination; if (dd != null) { if (dd.Page != null) return (doc.Pages.IndexOf(dd.Page) + tocPages + 1, null); else if (dd.PageIndex.HasValue) // NOTE: this loses the exact positioning on the target page, to fix create exact destination type copy: return (dd.PageIndex.Value + tocPages + 1, new DestinationFit(dd.PageIndex.Value + tocPages + 1)); } return (-1, null); } void MakeToc(OutlineNodeCollection nodes, int level, bool dryRun) { foreach (var node in nodes) { var (pageIdx, newDest) = PageIdxFromDest(node.Dest); // Ignore destinations without a target page: if (pageIdx >= 0) { top = tl.MarginTop + tl.ContentHeight + gap; if (drawCaption) { if (!dryRun) page.Graphics.DrawString("Table of Contents", tl.DefaultFormat, new PointF(margin, margin)); top += 24; drawCaption = false; } tl.Clear(); tl.MarginLeft = margin + levelOffset * level; tl.MarginTop = top; var run = tl.Append(node.Title); tl.AppendParagraphBreak(); tl.PerformLayout(true); if (!tl.ContentHeightFitsInBounds) { if (dryRun) ++tocPages; else page = doc.Pages.Insert(doc.Pages.IndexOf(page) + 1); InitLayout(level); top = tl.MarginTop; tl.PerformLayout(true); } if (!dryRun) { // Draw outline text: page.Graphics.DrawTextLayout(tl, PointF.Empty); // Draw page number: var pageNo = (pageIdx + 1).ToString(); var pageW = page.Graphics.MeasureString(pageNo, tl.DefaultFormat).Width; var trcs = tl.GetTextRects(run.CodePointIndex, run.CodePointCount, true, true); var trc = trcs[trcs.Count - 1]; var rc = new RectangleF(0, trc.Top, page.Size.Width - margin, trc.Height); page.Graphics.DrawString(pageNo, tl.DefaultFormat, rc, TextAlignment.Trailing, ParagraphAlignment.Near, false); // Draw dots: rc.X = trc.Right; rc.Width = page.Size.Width - trc.Right - margin - pageW - dotW; var dots = new string('.', (int)(rc.Width / dotW) - 1); page.Graphics.DrawString(dots, tl.DefaultFormat, rc, TextAlignment.Trailing, ParagraphAlignment.Near, false); // Make link: rc = new RectangleF(tl.MarginLeft, tl.MarginTop, page.Size.Width - tl.MarginLeft - margin, trc.Bottom - trcs[0].Top); page.Annotations.Add(new LinkAnnotation(rc, newDest ?? node.Dest)); // Debug: draw red border on converted page index destinations, blue on untouched originals: // page.Graphics.DrawRectangle(rc, newDest != null ? Color.Red : Color.Blue); } } MakeToc(node.Children, level + 1, dryRun); } } } } }