// 完毕:
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);
}
}
}
}
}