TocFromOutlines.cs
//
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © 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
{
// このサンプルでは、既存の PDF のアウトラインコレクションを使用して目次を作成し、
// その目次をドキュメントの先頭に挿入する方法を紹介しています。
public class TocFromOutlines
{
public int CreatePDF(Stream stream)
{
// 目次のレイアウト設定です。
var margin = 36;
var levelOffset = 12;
// ページ番号の横の空白を設定します。
var pageSpace = 24;
// 目次項目の行間を設定します。
var gap = 4;
var doc = new GcPdfDocument();
using var fs = File.OpenRead(Path.Combine("Resources", "PDFs", "guide-wetland-birds.pdf"));
doc.Load(fs);
// 計算用のページを追加し、テキストレイアウトを作成します。
var page = doc.Pages.Add();
var tl = page.Graphics.CreateTextLayout();
InitLayout(0);
// ドットの幅を計算します。
var dotW = page.Graphics.MeasureString(new string('.', 12), tl.DefaultFormat).Width / 12;
// 目次のページ数をカウントします。
float top = margin;
int tocPages = 0;
bool drawCaption = true;
MakeToc(doc.Outlines, 0, true);
// ドキュメントに目次を挿入します。
doc.Pages.RemoveAt(doc.Pages.Count - 1);
page = doc.Pages.Insert(0);
InitLayout(0);
top = margin;
drawCaption = true;
MakeToc(doc.Outlines, 0, false);
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)
// 注:この場合、対象ページでの正確な位置が失われるため、正確な遷移先のコピーを作成するよう修正します。
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);
// 対象ページがない遷移先は無視します。
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)
{
// アウトラインテキストを描画します。
page.Graphics.DrawTextLayout(tl, PointF.Empty);
// ページ番号を描画します。
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);
// ドットを描画します。
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);
// リンクを作成します。
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));
// デバッグ:変換されたページインデックスには赤枠を描画し、そのままのものには青枠を描画します。
// page.Graphics.DrawRectangle(rc, newDest != null ? Color.Red : Color.Blue);
}
}
MakeToc(node.Children, level + 1, dryRun);
}
}
}
}
}