using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
namespace PdfiumViewer
{
///
/// Control to render PDF documents.
///
public class PdfRenderer : PanningZoomingScrollControl
{
private static readonly Padding PageMargin = new Padding(4);
private int _height;
private int _maxWidth;
private int _maxHeight;
private double _documentScaleFactor;
private bool _disposed;
private double _scaleFactor;
private ShadeBorder _shadeBorder = new ShadeBorder();
private int _suspendPaintCount;
private ToolTip _toolTip;
private PdfViewerZoomMode _zoomMode;
private bool _pageCacheValid;
private readonly List _pageCache = new List();
private int _visiblePageStart;
private int _visiblePageEnd;
private PageLink _cachedLink;
private DragState _dragState;
private PdfRotation _rotation;
private List[] _markers;
///
/// The associated PDF document.
///
public IPdfDocument Document { get; private set; }
///
/// Gets or sets a value indicating whether the user can give the focus to this control using the TAB key.
///
///
///
/// true if the user can give the focus to the control using the TAB key; otherwise, false. The default is true.Note:This property will always return true for an instance of the class.
///
/// 1
[DefaultValue(true)]
public new bool TabStop
{
get { return base.TabStop; }
set { base.TabStop = value; }
}
///
/// Gets or sets the currently focused page.
///
public int Page
{
get
{
if (Document == null || !_pageCacheValid)
return 0;
int top = -DisplayRectangle.Top;
int bottom = top + GetScrollClientArea().Height;
for (int page = 0; page < Document.PageSizes.Count; page++)
{
var pageCache = _pageCache[page].OuterBounds;
if (top - 10 < pageCache.Top)
{
// If more than 50% of the page is hidden, return the previous page.
int hidden = pageCache.Bottom - bottom;
if (hidden > 0 && (double)hidden / pageCache.Height > 0.5 && page > 0)
return page - 1;
return page;
}
}
return Document.PageCount - 1;
}
set
{
if (Document == null)
{
SetDisplayRectLocation(new Point(0, 0));
}
else
{
int page = Math.Min(Math.Max(value, 0), Document.PageCount - 1);
SetDisplayRectLocation(new Point(0, -_pageCache[page].OuterBounds.Top));
}
}
}
///
/// Get the outer bounds of the page.
///
/// The page to get the bounds for.
/// The bounds of the page.
public Rectangle GetOuterBounds(int page)
{
if (Document == null || !_pageCacheValid)
return Rectangle.Empty;
page = Math.Min(Math.Max(page, 0), Document.PageCount - 1);
return _pageCache[page].OuterBounds;
}
///
/// Gets or sets the way the document should be zoomed initially.
///
public PdfViewerZoomMode ZoomMode
{
get { return _zoomMode; }
set
{
_zoomMode = value;
PerformLayout();
}
}
///
/// Gets or sets the current rotation of the PDF document.
///
public PdfRotation Rotation
{
get { return _rotation; }
set
{
if (_rotation != value)
{
_rotation = value;
ResetFromRotation();
}
}
}
///
/// Gets a collection with all markers.
///
public PdfMarkerCollection Markers { get; }
///
/// Initializes a new instance of the PdfRenderer class.
///
public PdfRenderer()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint, true);
TabStop = true;
_toolTip = new ToolTip();
Markers = new PdfMarkerCollection();
Markers.CollectionChanged += Markers_CollectionChanged;
}
private void Markers_CollectionChanged(object sender, EventArgs e)
{
RedrawMarkers();
}
///
/// Converts client coordinates to PDF coordinates.
///
/// Client coordinates to get the PDF location for.
/// The location in a PDF page or a PdfPoint with IsValid false when the coordinates do not match a PDF page.
public PdfPoint PointToPdf(Point location)
{
if (Document == null)
return PdfPoint.Empty;
var offset = GetScrollOffset();
location.Offset(-offset.Width, -offset.Height);
for (int page = 0; page < Document.PageSizes.Count; page++)
{
var pageCache = _pageCache[page];
if (pageCache.OuterBounds.Contains(location))
{
if (pageCache.Bounds.Contains(location))
{
location = new Point(
location.X - pageCache.Bounds.X,
location.Y - pageCache.Bounds.Y
);
var translated = TranslatePointToPdf(pageCache.Bounds.Size, Document.PageSizes[page], location);
translated = Document.PointToPdf(page, new Point((int)translated.X, (int)translated.Y));
return new PdfPoint(page, translated);
}
break;
}
}
return PdfPoint.Empty;
}
///
/// Converts a PDF point to a client point.
///
/// The PDF point to convert.
/// The location of the point in client coordinates.
public Point PointFromPdf(PdfPoint point)
{
var offset = GetScrollOffset();
var pageBounds = _pageCache[point.Page].Bounds;
var translated = Document.PointFromPdf(point.Page, point.Location);
var location = TranslatePointFromPdf(pageBounds.Size, Document.PageSizes[point.Page], translated);
return new Point(
pageBounds.Left + offset.Width + location.X,
pageBounds.Top + offset.Height + location.Y
);
}
///
/// Converts client coordinates to PDF bounds.
///
/// The client coordinates to convert.
/// The PDF bounds.
public PdfRectangle BoundsToPdf(Rectangle bounds)
{
if (Document == null)
return PdfRectangle.Empty;
var offset = GetScrollOffset();
bounds.Offset(-offset.Width, -offset.Height);
for (int page = 0; page < Document.PageSizes.Count; page++)
{
var pageCache = _pageCache[page];
if (pageCache.OuterBounds.Contains(bounds.Location))
{
if (pageCache.Bounds.Contains(bounds.Location))
{
var topLeft = new Point(
bounds.Left - pageCache.Bounds.Left,
bounds.Top - pageCache.Bounds.Top
);
var bottomRight = new Point(
bounds.Right - pageCache.Bounds.Left,
bounds.Bottom - pageCache.Bounds.Top
);
var translatedTopLeft = TranslatePointToPdf(pageCache.Bounds.Size, Document.PageSizes[page], topLeft);
var translatedBottomRight = TranslatePointToPdf(pageCache.Bounds.Size, Document.PageSizes[page], bottomRight);
var translated = Document.RectangleToPdf(
page,
new Rectangle(
(int)translatedTopLeft.X,
(int)translatedTopLeft.Y,
(int)(translatedBottomRight.X - translatedTopLeft.X),
(int)(translatedBottomRight.Y - translatedTopLeft.Y)
)
);
return new PdfRectangle(page, translated);
}
break;
}
}
return PdfRectangle.Empty;
}
///
/// Converts PDF bounds to client bounds.
///
/// The PDF bounds to convert.
/// The bounds of the PDF bounds in client coordinates.
public Rectangle BoundsFromPdf(PdfRectangle bounds)
{
return BoundsFromPdf(bounds, true);
}
private Rectangle BoundsFromPdf(PdfRectangle bounds, bool translateOffset)
{
var offset = translateOffset ? GetScrollOffset() : Size.Empty;
var pageBounds = _pageCache[bounds.Page].Bounds;
var pageSize = Document.PageSizes[bounds.Page];
var translated = Document.RectangleFromPdf(
bounds.Page,
bounds.Bounds
);
var topLeft = TranslatePointFromPdf(pageBounds.Size, pageSize, new PointF(translated.Left, translated.Top));
var bottomRight = TranslatePointFromPdf(pageBounds.Size, pageSize, new PointF(translated.Right, translated.Bottom));
return new Rectangle(
pageBounds.Left + offset.Width + Math.Min(topLeft.X, bottomRight.X),
pageBounds.Top + offset.Height + Math.Min(topLeft.Y, bottomRight.Y),
Math.Abs(bottomRight.X - topLeft.X),
Math.Abs(bottomRight.Y - topLeft.Y)
);
}
private PointF TranslatePointToPdf(Size size, SizeF pageSize, Point point)
{
switch (Rotation)
{
case PdfRotation.Rotate90:
point = new Point(size.Height - point.Y, point.X);
size = new Size(size.Height, size.Width);
break;
case PdfRotation.Rotate180:
point = new Point(size.Width - point.X, size.Height - point.Y);
break;
case PdfRotation.Rotate270:
point = new Point(point.Y, size.Width - point.X);
size = new Size(size.Height, size.Width);
break;
}
return new PointF(
((float)point.X / size.Width) * pageSize.Width,
((float)point.Y / size.Height) * pageSize.Height
);
}
private Point TranslatePointFromPdf(Size size, SizeF pageSize, PointF point)
{
switch (Rotation)
{
case PdfRotation.Rotate90:
point = new PointF(pageSize.Height - point.Y, point.X);
pageSize = new SizeF(pageSize.Height, pageSize.Width);
break;
case PdfRotation.Rotate180:
point = new PointF(pageSize.Width - point.X, pageSize.Height - point.Y);
break;
case PdfRotation.Rotate270:
point = new PointF(point.Y, pageSize.Width - point.X);
pageSize = new SizeF(pageSize.Height, pageSize.Width);
break;
}
return new Point(
(int)((point.X / pageSize.Width) * size.Width),
(int)((point.Y / pageSize.Height) * size.Height)
);
}
private Size GetScrollOffset()
{
var bounds = GetScrollClientArea();
int maxWidth = (int)(_maxWidth * _scaleFactor) + ShadeBorder.Size.Horizontal + PageMargin.Horizontal;
int leftOffset = (HScroll ? DisplayRectangle.X : (bounds.Width - maxWidth) / 2) + maxWidth / 2;
int topOffset = VScroll ? DisplayRectangle.Y : 0;
return new Size(leftOffset, topOffset);
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnLayout(LayoutEventArgs levent)
{
base.OnLayout(levent);
UpdateScrollbars();
}
///
/// Called when the zoom level changes.
///
/// The event args.
protected override void OnZoomChanged(EventArgs e)
{
base.OnZoomChanged(e);
UpdateScrollbars();
}
///
/// Load a into the control.
///
/// Document to load.
public void Load(IPdfDocument document)
{
if (document == null)
throw new ArgumentNullException("document");
if (document.PageCount == 0)
throw new ArgumentException("Document does not contain any pages", "document");
Document = document;
SetDisplayRectLocation(new Point(0, 0));
ReloadDocument();
}
private void ReloadDocument()
{
_height = 0;
_maxWidth = 0;
_maxHeight = 0;
foreach (var size in Document.PageSizes)
{
var translated = TranslateSize(size);
_height += (int)translated.Height;
_maxWidth = Math.Max((int)translated.Width, _maxWidth);
_maxHeight = Math.Max((int)translated.Height, _maxHeight);
}
_documentScaleFactor = _maxHeight != 0 ? (double)_maxWidth / _maxHeight : 0D;
_markers = null;
UpdateScrollbars();
Invalidate();
}
private void UpdateScrollbars()
{
if (Document == null)
return;
UpdateScaleFactor(ScrollBars.Both);
var bounds = GetScrollClientArea(ScrollBars.Both);
var documentSize = GetDocumentBounds().Size;
bool horizontalVisible = documentSize.Width > bounds.Width;
if (!horizontalVisible)
{
UpdateScaleFactor(ScrollBars.Vertical);
documentSize = GetDocumentBounds().Size;
}
_suspendPaintCount++;
try
{
SetDisplaySize(documentSize);
}
finally
{
_suspendPaintCount--;
}
RebuildPageCache();
}
private void RebuildPageCache()
{
if (Document == null || _suspendPaintCount > 0)
return;
_pageCacheValid = true;
int maxWidth = (int)(_maxWidth * _scaleFactor) + ShadeBorder.Size.Horizontal + PageMargin.Horizontal;
int leftOffset = -maxWidth / 2;
int offset = 0;
for (int page = 0; page < Document.PageSizes.Count; page++)
{
var size = TranslateSize(Document.PageSizes[page]);
int height = (int)(size.Height * _scaleFactor);
int fullHeight = height + ShadeBorder.Size.Vertical + PageMargin.Vertical;
int width = (int)(size.Width * _scaleFactor);
int maxFullWidth = (int)(_maxWidth * _scaleFactor) + ShadeBorder.Size.Horizontal + PageMargin.Horizontal;
int fullWidth = width + ShadeBorder.Size.Horizontal + PageMargin.Horizontal;
int thisLeftOffset = leftOffset + (maxFullWidth - fullWidth) / 2;
while (_pageCache.Count <= page)
{
_pageCache.Add(new PageCache());
}
var pageCache = _pageCache[page];
if (pageCache.Image != null)
{
pageCache.Image.Dispose();
pageCache.Image = null;
}
pageCache.Links = null;
pageCache.Bounds = new Rectangle(
thisLeftOffset + ShadeBorder.Size.Left + PageMargin.Left,
offset + ShadeBorder.Size.Top + PageMargin.Top,
width,
height
);
pageCache.OuterBounds = new Rectangle(
thisLeftOffset,
offset,
width + ShadeBorder.Size.Horizontal + PageMargin.Horizontal,
height + ShadeBorder.Size.Vertical + PageMargin.Vertical
);
offset += fullHeight;
}
}
private List GetPageLinks(int page)
{
var pageCache = _pageCache[page];
if (pageCache.Links == null)
{
pageCache.Links = new List();
foreach (var link in Document.GetPageLinks(page, pageCache.Bounds.Size).Links)
{
pageCache.Links.Add(new PageLink(link, BoundsFromPdf(new PdfRectangle(page, link.Bounds), false)));
}
}
return pageCache.Links;
}
private Rectangle GetScrollClientArea()
{
ScrollBars scrollBarsVisible;
if (HScroll && VScroll)
scrollBarsVisible = ScrollBars.Both;
else if (HScroll)
scrollBarsVisible = ScrollBars.Horizontal;
else if (VScroll)
scrollBarsVisible = ScrollBars.Vertical;
else
scrollBarsVisible = ScrollBars.None;
return GetScrollClientArea(scrollBarsVisible);
}
private Rectangle GetScrollClientArea(ScrollBars scrollbars)
{
return new Rectangle(
0,
0,
scrollbars == ScrollBars.Vertical || scrollbars == ScrollBars.Both ? Width - SystemInformation.VerticalScrollBarWidth : Width,
scrollbars == ScrollBars.Horizontal || scrollbars == ScrollBars.Both ? Height - SystemInformation.HorizontalScrollBarHeight : Height
);
}
private void UpdateScaleFactor(ScrollBars scrollBars)
{
var bounds = GetScrollClientArea(scrollBars);
// Scale factor determines what we need to multiply the dimensions
// of the metafile with to get the size in the control.
var zoomMode = CalculateZoomModeForFitBest(bounds);
if (zoomMode == PdfViewerZoomMode.FitHeight)
{
int height = bounds.Height - ShadeBorder.Size.Vertical - PageMargin.Vertical;
_scaleFactor = ((double)height / _maxHeight) * Zoom;
}
else
{
int width = bounds.Width - ShadeBorder.Size.Horizontal - PageMargin.Horizontal;
_scaleFactor = ((double)width / _maxWidth) * Zoom;
}
}
private PdfViewerZoomMode CalculateZoomModeForFitBest(Rectangle bounds)
{
if (ZoomMode != PdfViewerZoomMode.FitBest)
{
return ZoomMode;
}
var controlScaleFactor = (double)bounds.Width / bounds.Height;
return controlScaleFactor >= _documentScaleFactor ? PdfViewerZoomMode.FitHeight : PdfViewerZoomMode.FitWidth;
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnPaint(PaintEventArgs e)
{
if (Document == null || _suspendPaintCount > 0 || !_pageCacheValid)
return;
EnsureMarkers();
var offset = GetScrollOffset();
var bounds = GetScrollClientArea();
using (var brush = new SolidBrush(BackColor))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
_visiblePageStart = -1;
_visiblePageEnd = -1;
for (int page = 0; page < Document.PageSizes.Count; page++)
{
var pageCache = _pageCache[page];
var rectangle = pageCache.OuterBounds;
rectangle.Offset(offset.Width, offset.Height);
if (_visiblePageStart == -1)
{
if (rectangle.Bottom >= 0)
{
_visiblePageStart = page;
}
else if (pageCache.Image != null)
{
pageCache.Image.Dispose();
pageCache.Image = null;
}
}
if (rectangle.Top > bounds.Height)
{
if (_visiblePageEnd == -1)
_visiblePageEnd = page - 1;
if (pageCache.Image != null)
{
pageCache.Image.Dispose();
pageCache.Image = null;
}
}
if (e.ClipRectangle.IntersectsWith(rectangle))
{
var pageBounds = pageCache.Bounds;
pageBounds.Offset(offset.Width, offset.Height);
e.Graphics.FillRectangle(Brushes.White, pageBounds);
DrawPageImage(e.Graphics, page, pageBounds);
_shadeBorder.Draw(e.Graphics, pageBounds);
DrawMarkers(e.Graphics, page);
}
}
if (_visiblePageStart == -1)
_visiblePageStart = 0;
if (_visiblePageEnd == -1)
_visiblePageEnd = Document.PageCount - 1;
}
private void DrawPageImage(Graphics graphics, int page, Rectangle pageBounds)
{
var pageCache = _pageCache[page];
if (pageCache.Image == null)
pageCache.Image = Document.Render(page, pageBounds.Width, pageBounds.Height, graphics.DpiX, graphics.DpiY, Rotation, PdfRenderFlags.Annotations);
graphics.DrawImageUnscaled(pageCache.Image, pageBounds.Location);
}
///
/// Gets the document bounds.
///
/// The document bounds.
protected override Rectangle GetDocumentBounds()
{
int height = (int)(_height * _scaleFactor + (ShadeBorder.Size.Vertical + PageMargin.Vertical) * Document.PageCount);
int width = (int)(_maxWidth * _scaleFactor + ShadeBorder.Size.Horizontal + PageMargin.Horizontal);
var center = new Point(
DisplayRectangle.Width / 2,
DisplayRectangle.Height / 2
);
if (
DisplayRectangle.Width > ClientSize.Width ||
DisplayRectangle.Height > ClientSize.Height
) {
center.X += DisplayRectangle.Left;
center.Y += DisplayRectangle.Top;
}
return new Rectangle(
center.X - width / 2,
center.Y - height / 2,
width,
height
);
}
///
/// Called whent he cursor changes.
///
/// The event args.
protected override void OnSetCursor(SetCursorEventArgs e)
{
_cachedLink = null;
if (_pageCacheValid)
{
var offset = GetScrollOffset();
var location = new Point(
e.Location.X - offset.Width,
e.Location.Y - offset.Height
);
for (int page = _visiblePageStart; page <= _visiblePageEnd; page++)
{
foreach (var link in GetPageLinks(page))
{
if (link.Bounds.Contains(location))
{
_cachedLink = link;
e.Cursor = Cursors.Hand;
return;
}
}
}
}
base.OnSetCursor(e);
}
/// Raises the event.
/// A that contains the event data.
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
_dragState = null;
if (_cachedLink != null)
{
_dragState = new DragState
{
Link = _cachedLink.Link,
Location = e.Location
};
}
}
/// Raises the event.
/// A that contains the event data.
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (_dragState == null)
return;
int dx = Math.Abs(e.Location.X - _dragState.Location.X);
int dy = Math.Abs(e.Location.Y - _dragState.Location.Y);
var link = _dragState.Link;
_dragState = null;
if (link == null)
return;
if (dx <= SystemInformation.DragSize.Width && dy <= SystemInformation.DragSize.Height)
{
var linkClickEventArgs = new LinkClickEventArgs(link);
HandleLinkClick(linkClickEventArgs);
}
}
private void HandleLinkClick(LinkClickEventArgs e)
{
OnLinkClick(e);
if (e.Handled)
return;
if (e.Link.TargetPage.HasValue)
Page = e.Link.TargetPage.Value;
if (e.Link.Uri != null)
{
try
{
Process.Start(e.Link.Uri);
}
catch
{
// Some browsers (Firefox) will cause an exception to
// be thrown (when it auto-updates).
}
}
}
///
/// Occurs when a link in the pdf document is clicked.
///
[Category("Action")]
[Description("Occurs when a link in the pdf document is clicked.")]
public event LinkClickEventHandler LinkClick;
///
/// Called when a link is clicked.
///
/// The event args.
protected virtual void OnLinkClick(LinkClickEventArgs e)
{
var handler = LinkClick;
if (handler != null)
handler(this, e);
}
///
/// Rotate the PDF document left.
///
public void RotateLeft()
{
Rotation = (PdfRotation)(((int)Rotation + 3) % 4);
}
///
/// Rotate the PDF document right.
///
public void RotateRight()
{
Rotation = (PdfRotation)(((int)Rotation + 1) % 4);
}
private void ResetFromRotation()
{
var offsetX = (double)DisplayRectangle.Left / DisplayRectangle.Width;
var offsetY = (double)DisplayRectangle.Top / DisplayRectangle.Height;
ReloadDocument();
SetDisplayRectLocation(new Point(
(int)(DisplayRectangle.Width * offsetX),
(int)(DisplayRectangle.Height * offsetY)
));
}
private SizeF TranslateSize(SizeF size)
{
switch (Rotation)
{
case PdfRotation.Rotate90:
case PdfRotation.Rotate270:
return new SizeF(size.Height, size.Width);
default:
return size;
}
}
///
/// Called when the zoom level changes.
///
/// The new zoom level.
/// The location to focus on.
protected override void SetZoom(double zoom, Point? focus)
{
Point location;
if (focus.HasValue)
{
var bounds = GetDocumentBounds();
location = new Point(
focus.Value.X - bounds.X,
focus.Value.Y - bounds.Y
);
}
else
{
var bounds = _pageCacheValid
? _pageCache[Page].Bounds
: GetDocumentBounds();
location = new Point(
bounds.X,
bounds.Y
);
}
double oldScale = Zoom;
base.SetZoom(zoom, null);
var newLocation = new Point(
(int)(location.X * (zoom / oldScale)),
(int)(location.Y * (zoom / oldScale))
);
SetDisplayRectLocation(
new Point(
DisplayRectangle.Left - (newLocation.X - location.X),
DisplayRectangle.Top - (newLocation.Y - location.Y)
),
false
);
}
private void RedrawMarkers()
{
_markers = null;
Invalidate();
}
private void EnsureMarkers()
{
if (_markers != null)
return;
_markers = new List[_pageCache.Count];
foreach (var marker in Markers)
{
if (marker.Page < 0 || marker.Page >= _markers.Length)
continue;
if (_markers[marker.Page] == null)
_markers[marker.Page] = new List();
_markers[marker.Page].Add(marker);
}
}
private void DrawMarkers(Graphics graphics, int page)
{
var markers = _markers[page];
if (markers == null)
return;
foreach (var marker in markers)
{
marker.Draw(this, graphics);
}
}
///
/// Scroll the PDF bounds into view.
///
/// The PDF bounds to scroll into view.
public void ScrollIntoView(PdfRectangle bounds)
{
ScrollIntoView(BoundsFromPdf(bounds));
}
///
/// Scroll the client rectangle into view.
///
/// The client rectangle to scroll into view.
public void ScrollIntoView(Rectangle rectangle)
{
var clientArea = GetScrollClientArea();
if (rectangle.Top < 0 || rectangle.Bottom > clientArea.Height)
{
var displayRectangle = DisplayRectangle;
int center = rectangle.Top + rectangle.Height / 2;
int documentCenter = center - displayRectangle.Y;
int displayCenter = clientArea.Height / 2;
int offset = documentCenter - displayCenter;
SetDisplayRectLocation(new Point(
displayRectangle.X,
-offset
));
}
}
///
/// Releases the unmanaged resources used by the and its child controls and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
if (_shadeBorder != null)
{
_shadeBorder.Dispose();
_shadeBorder = null;
}
if (_toolTip != null)
{
_toolTip.Dispose();
_toolTip = null;
}
foreach (var pageCache in _pageCache)
{
if (pageCache.Image != null)
{
pageCache.Image.Dispose();
pageCache.Image = null;
}
}
_disposed = true;
}
base.Dispose(disposing);
}
private class PageCache
{
public List Links { get; set; }
public Rectangle Bounds { get; set; }
public Rectangle OuterBounds { get; set; }
public Image Image { get; set; }
}
private class PageLink
{
public PdfPageLink Link { get; }
public Rectangle Bounds { get; }
public PageLink(PdfPageLink link, Rectangle bounds)
{
Link = link;
Bounds = bounds;
}
}
private class DragState
{
public PdfPageLink Link { get; set; }
public Point Location { get; set; }
}
}
}