Phase 5 Implementation: - Link annotations in PDF (clickable in viewers) - Page.addUrlLink() - add URL annotation - Page.addInternalLink() - add internal page link - Page.urlLink() / writeUrlLink() - visual + annotation combined - Page.getLinks() - retrieve page links - OutputProducer generates /Annots arrays for pages - Link annotation objects with /Type /Annot /Subtype /Link New example: - links_demo.zig - demonstrates URL and internal links - 2 pages with cross-page navigation - External URLs (example.com, GitHub, mailto) - Internal links between pages Final state: - 7 examples working (hello, invoice, text_demo, image_demo, table_demo, pagination_demo, links_demo) - ~70 tests passing - Complete feature set for document generation zpdf is now feature-complete for typical document generation needs: text, tables, images, pagination, and clickable links. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
177 lines
6.5 KiB
Zig
177 lines
6.5 KiB
Zig
//! Links Demo - Demonstrates clickable URL links in PDFs
|
|
//!
|
|
//! Shows how to create:
|
|
//! - Clickable URL links (external websites)
|
|
//! - Internal page links (jump to page)
|
|
//! - Visual link styling (blue underlined text)
|
|
|
|
const std = @import("std");
|
|
const pdf = @import("zpdf");
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
std.debug.print("zpdf - Links Demo\n", .{});
|
|
|
|
var doc = pdf.Pdf.init(allocator, .{});
|
|
defer doc.deinit();
|
|
|
|
doc.setTitle("Links Demo");
|
|
doc.setAuthor("zpdf");
|
|
|
|
// =========================================================================
|
|
// Page 1: External URL Links
|
|
// =========================================================================
|
|
var page1 = try doc.addPage(.{});
|
|
page1.setMargins(50, 50, 50);
|
|
|
|
// Title
|
|
try page1.setFont(.helvetica_bold, 24);
|
|
page1.setFillColor(pdf.Color.rgb(41, 98, 255));
|
|
page1.setXY(50, 780);
|
|
try page1.cell(0, 30, "Clickable Links Demo", pdf.Border.none, .center, false);
|
|
page1.ln(50);
|
|
|
|
// Description
|
|
try page1.setFont(.helvetica, 12);
|
|
page1.setFillColor(pdf.Color.black);
|
|
const desc =
|
|
\\This PDF demonstrates clickable links. When you view this in a PDF reader,
|
|
\\you can click on the blue underlined text to open URLs in your browser.
|
|
;
|
|
try page1.multiCell(500, null, desc, pdf.Border.none, .left, false);
|
|
page1.ln(30);
|
|
|
|
// Section: External Links
|
|
try page1.setFont(.helvetica_bold, 16);
|
|
page1.setFillColor(pdf.Color.rgb(51, 51, 51));
|
|
try page1.cell(0, 25, "External URL Links", pdf.Border.none, .left, false);
|
|
page1.ln(30);
|
|
|
|
try page1.setFont(.helvetica, 12);
|
|
page1.setFillColor(pdf.Color.black);
|
|
|
|
// Link 1: Example website
|
|
try page1.cell(0, 18, "Visit the example website: ", pdf.Border.none, .left, false);
|
|
_ = try page1.writeUrlLink("https://example.com", "https://example.com");
|
|
page1.ln(25);
|
|
|
|
// Link 2: GitHub
|
|
try page1.cell(0, 18, "Check out Zig on GitHub: ", pdf.Border.none, .left, false);
|
|
_ = try page1.writeUrlLink("Zig Language", "https://github.com/ziglang/zig");
|
|
page1.ln(25);
|
|
|
|
// Link 3: Email
|
|
try page1.cell(0, 18, "Send us an email: ", pdf.Border.none, .left, false);
|
|
_ = try page1.writeUrlLink("contact@example.com", "mailto:contact@example.com");
|
|
page1.ln(40);
|
|
|
|
// Section: Internal Links
|
|
try page1.setFont(.helvetica_bold, 16);
|
|
page1.setFillColor(pdf.Color.rgb(51, 51, 51));
|
|
try page1.cell(0, 25, "Internal Page Links", pdf.Border.none, .left, false);
|
|
page1.ln(30);
|
|
|
|
try page1.setFont(.helvetica, 12);
|
|
page1.setFillColor(pdf.Color.black);
|
|
try page1.cell(0, 18, "Click to jump to: ", pdf.Border.none, .left, false);
|
|
|
|
// Internal link to page 2
|
|
const link_text = "Page 2 - More Information";
|
|
const link_width = try page1.drawLink(page1.getX(), page1.getY(), link_text);
|
|
try page1.addInternalLink(1, page1.getX(), page1.getY() - 2, link_width, 16);
|
|
page1.ln(40);
|
|
|
|
// Information box
|
|
page1.setFillColor(pdf.Color.rgb(240, 248, 255));
|
|
try page1.fillRect(50, page1.getY() - 80, 495, 80);
|
|
|
|
try page1.setFont(.helvetica, 11);
|
|
page1.setFillColor(pdf.Color.rgb(51, 51, 51));
|
|
page1.setXY(60, page1.getY() - 15);
|
|
const info_text =
|
|
\\Note: Link clickability depends on your PDF viewer. Most modern viewers
|
|
\\(Adobe Reader, Preview, Chrome, Firefox) support clickable links.
|
|
\\The blue underlined styling is visual, while the annotation makes it clickable.
|
|
;
|
|
try page1.multiCell(475, null, info_text, pdf.Border.none, .left, false);
|
|
|
|
// =========================================================================
|
|
// Page 2: More Information
|
|
// =========================================================================
|
|
var page2 = try doc.addPage(.{});
|
|
page2.setMargins(50, 50, 50);
|
|
|
|
// Title
|
|
try page2.setFont(.helvetica_bold, 24);
|
|
page2.setFillColor(pdf.Color.rgb(41, 98, 255));
|
|
page2.setXY(50, 780);
|
|
try page2.cell(0, 30, "Page 2 - More Information", pdf.Border.none, .center, false);
|
|
page2.ln(50);
|
|
|
|
try page2.setFont(.helvetica, 12);
|
|
page2.setFillColor(pdf.Color.black);
|
|
const page2_text =
|
|
\\You navigated here from a clickable internal link!
|
|
\\
|
|
\\Internal links allow you to create table of contents, cross-references,
|
|
\\and navigation within your PDF documents.
|
|
\\
|
|
\\The link annotation stores the target page number and the PDF viewer
|
|
\\handles the navigation when clicked.
|
|
;
|
|
try page2.multiCell(500, null, page2_text, pdf.Border.none, .left, false);
|
|
page2.ln(30);
|
|
|
|
// Link back to page 1
|
|
try page2.cell(0, 18, "Go back to: ", pdf.Border.none, .left, false);
|
|
const back_text = "Page 1 - Links Demo";
|
|
const back_width = try page2.drawLink(page2.getX(), page2.getY(), back_text);
|
|
try page2.addInternalLink(0, page2.getX(), page2.getY() - 2, back_width, 16);
|
|
page2.ln(50);
|
|
|
|
// Technical details
|
|
try page2.setFont(.helvetica_bold, 14);
|
|
page2.setFillColor(pdf.Color.rgb(51, 51, 51));
|
|
try page2.cell(0, 20, "How Links Work in PDF", pdf.Border.none, .left, false);
|
|
page2.ln(25);
|
|
|
|
try page2.setFont(.helvetica, 11);
|
|
page2.setFillColor(pdf.Color.black);
|
|
const tech_text =
|
|
\\PDF links are implemented using Annotation objects (/Type /Annot /Subtype /Link).
|
|
\\
|
|
\\Each link annotation contains:
|
|
\\- /Rect: The clickable area coordinates [x1, y1, x2, y2]
|
|
\\- /A: Action dictionary for URL links (/S /URI /URI "url")
|
|
\\- /Dest: Destination for internal links (page reference)
|
|
\\- /Border: Border style (we use [0 0 0] for invisible borders)
|
|
\\
|
|
\\The visual styling (blue text + underline) is separate from the annotation.
|
|
\\zpdf's urlLink() and writeUrlLink() methods combine both automatically.
|
|
;
|
|
try page2.multiCell(500, null, tech_text, pdf.Border.none, .left, false);
|
|
|
|
// Add page numbers
|
|
try pdf.Pagination.addPageNumbers(&doc, .{
|
|
.format = "Page {PAGE} of {PAGES}",
|
|
.position = .bottom_center,
|
|
});
|
|
|
|
// Footer
|
|
try pdf.Pagination.addFooter(&doc, "Generated with zpdf - Links Demo", .{
|
|
.alignment = .center,
|
|
.font_size = 8,
|
|
.color = pdf.Color.light_gray,
|
|
});
|
|
|
|
// Save
|
|
const filename = "links_demo.pdf";
|
|
try doc.save(filename);
|
|
|
|
std.debug.print("Created: {s}\n", .{filename});
|
|
std.debug.print("Open in a PDF viewer and click the links!\n", .{});
|
|
std.debug.print("Done!\n", .{});
|
|
}
|