mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Add Links to Pdf Generation
This commit is contained in:
@@ -1295,6 +1295,78 @@
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAddLinkToPage()
|
||||
{
|
||||
var builder = new PdfDocumentBuilder();
|
||||
var page = builder.AddPage(PageSize.A4);
|
||||
var font = builder.AddStandard14Font(Standard14Font.Helvetica);
|
||||
|
||||
var linkArea = new PdfRectangle(25, 690, 200, 720);
|
||||
page.AddLink("https://github.com", linkArea);
|
||||
|
||||
var bytes = builder.Build();
|
||||
WriteFile(nameof(CanAddLinkToPage), bytes);
|
||||
|
||||
using (var document = PdfDocument.Open(bytes))
|
||||
{
|
||||
Assert.Equal(1, document.NumberOfPages);
|
||||
var page1 = document.GetPage(1);
|
||||
|
||||
var annotations = page1.GetAnnotations().ToList();
|
||||
Assert.Single(annotations);
|
||||
|
||||
var linkAnnotation = annotations[0];
|
||||
Assert.Equal(Annotations.AnnotationType.Link, linkAnnotation.Type);
|
||||
Assert.Equal(linkArea, linkAnnotation.Rectangle);
|
||||
|
||||
// Verify the URI link target
|
||||
Assert.NotNull(linkAnnotation.Action);
|
||||
var uriAction = Assert.IsType<Actions.UriAction>(linkAnnotation.Action);
|
||||
Assert.Equal("https://github.com", uriAction.Uri);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAddInternalLinkToPage()
|
||||
{
|
||||
var builder = new PdfDocumentBuilder();
|
||||
var font = builder.AddStandard14Font(Standard14Font.Helvetica);
|
||||
|
||||
var page1 = builder.AddPage(PageSize.A4);
|
||||
var page2 = builder.AddPage(PageSize.A4);
|
||||
|
||||
var linkArea = new PdfRectangle(25, 690, 200, 720);
|
||||
var coordinates = new ExplicitDestinationCoordinates(25, 750);
|
||||
var destination = new ExplicitDestination(1, ExplicitDestinationType.XyzCoordinates, coordinates);
|
||||
page2.AddLink(destination, linkArea);
|
||||
|
||||
var bytes = builder.Build();
|
||||
WriteFile(nameof(CanAddInternalLinkToPage), bytes);
|
||||
|
||||
using (var document = PdfDocument.Open(bytes))
|
||||
{
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
|
||||
var page2Doc = document.GetPage(2);
|
||||
|
||||
var annotations = page2Doc.GetAnnotations().ToList();
|
||||
Assert.Single(annotations);
|
||||
|
||||
var linkAnnotation = annotations[0];
|
||||
Assert.Equal(Annotations.AnnotationType.Link, linkAnnotation.Type);
|
||||
Assert.Equal(linkArea, linkAnnotation.Rectangle);
|
||||
|
||||
// Verify the link destination
|
||||
Assert.NotNull(linkAnnotation.Action);
|
||||
var goToAction = Assert.IsType<Actions.GoToAction>(linkAnnotation.Action);
|
||||
Assert.Equal(1, goToAction.Destination.PageNumber);
|
||||
Assert.Equal(ExplicitDestinationType.XyzCoordinates, goToAction.Destination.Type);
|
||||
Assert.Equal(25, goToAction.Destination.Coordinates.Left);
|
||||
Assert.Equal(750, goToAction.Destination.Coordinates.Top);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFile(string name, byte[] bytes, string extension = "pdf")
|
||||
{
|
||||
try
|
||||
|
||||
178
src/UglyToad.PdfPig/Writer/LinkAnnotation.cs
Normal file
178
src/UglyToad.PdfPig/Writer/LinkAnnotation.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
namespace UglyToad.PdfPig.Writer
|
||||
{
|
||||
using UglyToad.PdfPig.Actions;
|
||||
using UglyToad.PdfPig.Annotations;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a link annotation that can be added to a PDF page.
|
||||
/// Link annotations provide clickable areas that can trigger actions such as navigating to another page or opening a URL.
|
||||
/// </summary>
|
||||
public class LinkAnnotation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the border style for the link annotation.
|
||||
/// This is overwritten by the <see cref="AnnotationBorder"/> if both are provided.
|
||||
/// </summary>
|
||||
public AnnotationBorder? AnnotationBorder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the border style for the link annotation.
|
||||
/// </summary>
|
||||
public BorderStyle? Border { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the border for the link annotation.
|
||||
/// </summary>
|
||||
public int? BorderWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rectangle defining the location and size of the link annotation on the page.
|
||||
/// </summary>
|
||||
public PdfRectangle Rect { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quadrilaterals defining the clickable regions of the link.
|
||||
/// These are typically used to define precise clickable areas that may not be rectangular.
|
||||
/// </summary>
|
||||
public IReadOnlyList<QuadPointsQuadrilateral> QuadPoints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the action to be performed when the link is activated.
|
||||
/// </summary>
|
||||
public PdfAction Action { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the border style for a link annotation.
|
||||
/// </summary>
|
||||
public enum BorderStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// A solid border.
|
||||
/// </summary>
|
||||
Solid,
|
||||
|
||||
/// <summary>
|
||||
/// A dashed border.
|
||||
/// </summary>
|
||||
Dashed,
|
||||
|
||||
/// <summary>
|
||||
/// A simulated embossed border that appears to be raised above the surface of the page.
|
||||
/// </summary>
|
||||
Beveled,
|
||||
|
||||
/// <summary>
|
||||
/// A simulated engraved border that appears to be recessed below the surface of the page.
|
||||
/// </summary>
|
||||
Inset,
|
||||
|
||||
/// <summary>
|
||||
/// An underline border drawn along the bottom of the annotation rectangle.
|
||||
/// </summary>
|
||||
Underline,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="LinkAnnotation"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to be performed when the link is activated.</param>
|
||||
/// <param name="rect">The rectangle defining the location and size of the link on the page.</param>
|
||||
/// <param name="annotationBorder">The border style for the link annotation. Optional, overwritten by <see cref="Border"/>.</param>
|
||||
/// <param name="borderStyle">The border style for the link annotation. Optional.</param>
|
||||
/// <param name="borderWidth">The width of the border for the link annotation. Optional.</param>
|
||||
/// <param name="quadPoints">The quadrilaterals defining the clickable regions. Optional.</param>
|
||||
public LinkAnnotation(
|
||||
PdfAction action,
|
||||
PdfRectangle rect,
|
||||
AnnotationBorder? annotationBorder = null,
|
||||
BorderStyle? borderStyle = null,
|
||||
int? borderWidth = null,
|
||||
IReadOnlyList<QuadPointsQuadrilateral>? quadPoints = null)
|
||||
{
|
||||
Action = action;
|
||||
Rect = rect;
|
||||
AnnotationBorder = annotationBorder;
|
||||
Border = borderStyle;
|
||||
BorderWidth = borderWidth;
|
||||
QuadPoints = quadPoints ?? new List<QuadPointsQuadrilateral>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this link annotation to a PDF dictionary token representation.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="DictionaryToken"/> representing this link annotation in PDF format.</returns>
|
||||
public DictionaryToken ToToken()
|
||||
{
|
||||
var dict = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
[NameToken.Type] = NameToken.Annot,
|
||||
[NameToken.Subtype] = NameToken.Link,
|
||||
[NameToken.Rect] = new ArrayToken([
|
||||
new NumericToken(Rect.BottomLeft.X),
|
||||
new NumericToken(Rect.BottomLeft.Y),
|
||||
new NumericToken(Rect.TopRight.X),
|
||||
new NumericToken(Rect.TopRight.Y)
|
||||
]),
|
||||
};
|
||||
|
||||
if (QuadPoints.Count > 0)
|
||||
{
|
||||
var quadPointsArray = new List<NumericToken>();
|
||||
foreach (var quad in QuadPoints)
|
||||
{
|
||||
foreach (var point in quad.Points)
|
||||
{
|
||||
quadPointsArray.Add(new NumericToken(point.X));
|
||||
quadPointsArray.Add(new NumericToken(point.Y));
|
||||
}
|
||||
}
|
||||
|
||||
dict.Add(NameToken.Quadpoints, new ArrayToken(quadPointsArray));
|
||||
}
|
||||
|
||||
if (AnnotationBorder != null)
|
||||
{
|
||||
var borderArray = new List<IToken>
|
||||
{
|
||||
new NumericToken(AnnotationBorder.HorizontalCornerRadius),
|
||||
new NumericToken(AnnotationBorder.VerticalCornerRadius),
|
||||
new NumericToken(AnnotationBorder.BorderWidth),
|
||||
};
|
||||
|
||||
if (AnnotationBorder.LineDashPattern != null && AnnotationBorder.LineDashPattern.Count > 0)
|
||||
{
|
||||
var dashArray = new List<NumericToken>();
|
||||
foreach (var dash in AnnotationBorder.LineDashPattern)
|
||||
{
|
||||
dashArray.Add(new NumericToken(dash));
|
||||
}
|
||||
borderArray.Add(new ArrayToken(dashArray));
|
||||
}
|
||||
dict.Add(NameToken.Border, new ArrayToken(borderArray));
|
||||
}
|
||||
|
||||
if (Border != null)
|
||||
{
|
||||
dict.Add(NameToken.Bs, new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
[NameToken.S] = Border switch
|
||||
{
|
||||
BorderStyle.Solid => NameToken.S,
|
||||
BorderStyle.Dashed => NameToken.D,
|
||||
BorderStyle.Beveled => NameToken.B,
|
||||
BorderStyle.Inset => NameToken.I,
|
||||
BorderStyle.Underline => NameToken.U,
|
||||
_ => NameToken.S,
|
||||
},
|
||||
[NameToken.W] = new NumericToken(BorderWidth ?? 1)
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
return new DictionaryToken(dict);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
using Graphics.Operations.TextPositioning;
|
||||
using Graphics.Operations.TextShowing;
|
||||
using Graphics.Operations.TextState;
|
||||
using Outline.Destinations;
|
||||
using Images;
|
||||
using PdfFonts;
|
||||
using Tokens;
|
||||
@@ -94,7 +95,7 @@
|
||||
private IPageContentStream currentStream;
|
||||
|
||||
// links to be resolved when all page references are available
|
||||
internal readonly List<(DictionaryToken token, PdfAction action)>? links;
|
||||
internal readonly List<(DictionaryToken token, PdfAction action)> links = [];
|
||||
|
||||
// maps fonts added using PdfDocumentBuilder to page font names
|
||||
private readonly Dictionary<Guid, NameToken> documentFonts = new Dictionary<Guid, NameToken>();
|
||||
@@ -827,6 +828,39 @@
|
||||
return new AddedImage(reference.Data, png.Width, png.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a URL link annotation to the page at the specified rectangle area.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to link to</param>
|
||||
/// <param name="linkArea">The rectangular area on the page that will be clickable</param>
|
||||
/// <returns>This page builder for method chaining</returns>
|
||||
public PdfPageBuilder AddLink(string url, PdfRectangle linkArea)
|
||||
{
|
||||
return AddLink(new LinkAnnotation(new UriAction(url), linkArea));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an internal document link annotation to the page at the specified rectangle area.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination within the current document to link to</param>
|
||||
/// <param name="linkArea">The rectangular area on the page that will be clickable</param>
|
||||
/// <returns>This page builder for method chaining</returns>
|
||||
public PdfPageBuilder AddLink(ExplicitDestination destination, PdfRectangle linkArea)
|
||||
{
|
||||
return AddLink(new LinkAnnotation(new GoToAction(destination), linkArea));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a link annotation to the page.
|
||||
/// </summary>
|
||||
/// <param name="link">The link annotation to add</param>
|
||||
/// <returns>This page builder for method chaining</returns>
|
||||
public PdfPageBuilder AddLink(LinkAnnotation link)
|
||||
{
|
||||
links.Add((link.ToToken(), link.Action));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy a page from unknown source to this page
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user