TocFromOutlines.cs
  1. //
  2. // This code is part of Document Solutions for PDF demos.
  3. // Copyright (c) MESCIUS inc. All rights reserved.
  4. //
  5. using System;
  6. using System.IO;
  7. using System.Drawing;
  8. using GrapeCity.Documents.Pdf;
  9. using GrapeCity.Documents.Text;
  10. using GrapeCity.Documents.Pdf.Annotations;
  11.  
  12. namespace DsPdfWeb.Demos
  13. {
  14. // This example shows how to use the Outlines collection of an existing PDF
  15. // to build a table of contents and insert that TOC at the top of the document.
  16. public class TocFromOutlines
  17. {
  18. public int CreatePDF(Stream stream)
  19. {
  20. // TOC layout setup:
  21. var margin = 36;
  22. var levelOffset = 12;
  23. // Horizontal space allocated for the page numbers:
  24. var pageSpace = 24;
  25. // Vertical gap between TOC entries:
  26. var gap = 4;
  27.  
  28. var doc = new GcPdfDocument();
  29. using var fs = File.OpenRead(Path.Combine("Resources", "PDFs", "guide-wetland-birds.pdf"));
  30. doc.Load(fs);
  31.  
  32. // Add sacrificial page and create text layout:
  33. var page = doc.Pages.Add();
  34. var tl = page.Graphics.CreateTextLayout();
  35. InitLayout(0);
  36. // Measure a dot:
  37. var dotW = page.Graphics.MeasureString(new string('.', 12), tl.DefaultFormat).Width / 12;
  38.  
  39. // Dry run to count the number of pages in the TOC:
  40. float top = margin;
  41. int tocPages = 0;
  42. bool drawCaption = true;
  43. MakeToc(doc.Outlines, 0, true);
  44.  
  45. // Live run to insert the TOC in the doc:
  46. doc.Pages.RemoveAt(doc.Pages.Count - 1);
  47. page = doc.Pages.Insert(0);
  48. InitLayout(0);
  49. top = margin;
  50. drawCaption = true;
  51. MakeToc(doc.Outlines, 0, false);
  52.  
  53. // Done:
  54. doc.Save(stream);
  55. return doc.Pages.Count;
  56.  
  57. void InitLayout(int level)
  58. {
  59. tl.MarginTop = margin;
  60. tl.MarginBottom = margin;
  61. tl.MarginLeft = margin + levelOffset * level;
  62. tl.MarginRight = margin + pageSpace;
  63. tl.MaxWidth = page.Size.Width;
  64. tl.MaxHeight = page.Size.Height;
  65. }
  66.  
  67. (int pageIdx, Destination newDest) PageIdxFromDest(DestinationBase dest)
  68. {
  69. IDestination dd;
  70. if (dest is DestinationRef df)
  71. doc.NamedDestinations.TryGetValue(df.Name, out dd);
  72. else
  73. dd = dest as Destination;
  74. if (dd != null)
  75. {
  76. if (dd.Page != null)
  77. return (doc.Pages.IndexOf(dd.Page) + tocPages + 1, null);
  78. else if (dd.PageIndex.HasValue)
  79. // NOTE: this loses the exact positioning on the target page, to fix create exact destination type copy:
  80. return (dd.PageIndex.Value + tocPages + 1, new DestinationFit(dd.PageIndex.Value + tocPages + 1));
  81. }
  82. return (-1, null);
  83. }
  84.  
  85. void MakeToc(OutlineNodeCollection nodes, int level, bool dryRun)
  86. {
  87. foreach (var node in nodes)
  88. {
  89. var (pageIdx, newDest) = PageIdxFromDest(node.Dest);
  90. // Ignore destinations without a target page:
  91. if (pageIdx >= 0)
  92. {
  93. top = tl.MarginTop + tl.ContentHeight + gap;
  94. if (drawCaption)
  95. {
  96. if (!dryRun)
  97. page.Graphics.DrawString("Table of Contents", tl.DefaultFormat, new PointF(margin, margin));
  98. top += 24;
  99. drawCaption = false;
  100. }
  101. tl.Clear();
  102. tl.MarginLeft = margin + levelOffset * level;
  103. tl.MarginTop = top;
  104. var run = tl.Append(node.Title);
  105. tl.AppendParagraphBreak();
  106. tl.PerformLayout(true);
  107. if (!tl.ContentHeightFitsInBounds)
  108. {
  109. if (dryRun)
  110. ++tocPages;
  111. else
  112. page = doc.Pages.Insert(doc.Pages.IndexOf(page) + 1);
  113. InitLayout(level);
  114. top = tl.MarginTop;
  115. tl.PerformLayout(true);
  116. }
  117. if (!dryRun)
  118. {
  119. // Draw outline text:
  120. page.Graphics.DrawTextLayout(tl, PointF.Empty);
  121. // Draw page number:
  122. var pageNo = (pageIdx + 1).ToString();
  123. var pageW = page.Graphics.MeasureString(pageNo, tl.DefaultFormat).Width;
  124. var trcs = tl.GetTextRects(run.CodePointIndex, run.CodePointCount, true, true);
  125. var trc = trcs[trcs.Count - 1];
  126. var rc = new RectangleF(0, trc.Top, page.Size.Width - margin, trc.Height);
  127. page.Graphics.DrawString(pageNo, tl.DefaultFormat, rc, TextAlignment.Trailing, ParagraphAlignment.Near, false);
  128. // Draw dots:
  129. rc.X = trc.Right;
  130. rc.Width = page.Size.Width - trc.Right - margin - pageW - dotW;
  131. var dots = new string('.', (int)(rc.Width / dotW) - 1);
  132. page.Graphics.DrawString(dots, tl.DefaultFormat, rc, TextAlignment.Trailing, ParagraphAlignment.Near, false);
  133. // Make link:
  134. rc = new RectangleF(tl.MarginLeft, tl.MarginTop, page.Size.Width - tl.MarginLeft - margin, trc.Bottom - trcs[0].Top);
  135. page.Annotations.Add(new LinkAnnotation(rc, newDest ?? node.Dest));
  136. // Debug: draw red border on converted page index destinations, blue on untouched originals:
  137. // page.Graphics.DrawRectangle(rc, newDest != null ? Color.Red : Color.Blue);
  138. }
  139. }
  140. MakeToc(node.Children, level + 1, dryRun);
  141. }
  142. }
  143. }
  144. }
  145. }
  146.