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);
- }
- }
- }
- }
- }
-