Hyperlink PDF adalah anotasi URI: sebuah persegi panjang yang menutupi suatu area halaman dan, ketika diklik, memberitahu penampil untuk membuka sebuah URL. Anotasi dan teks di bawahnya adalah objek yang sepenuhnya terpisah. PrintHyperlink milik HotPDF memaketkan keduanya menjadi satu panggilan, menggambar teksnya lalu menghitung persegi panjang anotasi dari metrik teks yang terrender. Kemudahan itu menyembunyikan detail yang layak dipahami sebelum Anda menulis kode produksi. Ia juga bukan keseluruhan ceritanya: AddURILink menempatkan area yang dapat diklik di atas konten yang Anda gambar sendiri, dan AddGoToLink menangani navigasi internal — keduanya dibahas di bawah
Bagaimana PrintHyperlink bekerja
PrintHyperlink berada di THPDFPage dan menerima empat argumen: koordinat X dan Y (dalam poin, titik asal kiri bawah, Y bertambah ke atas), string label yang digambar, dan target URL-nya. Secara internal ia memanggil TextOut dalam warna hyperlink saat itu, lalu langsung menghitung persegi panjang anotasi dari TextWidth dan TextHeight pada metrik font saat itu. Artinya font dan ukurannya harus disetel sebelum panggilan itu, dan keduanya tidak boleh berubah antara penggambaran label dan penempatan anotasi, karena keduanya diselesaikan dalam panggilan yang sama
Warna defaultnya adalah clBlue. SetRGBHyperlinkColor mengubahnya hanya untuk panggilan berikutnya; ia tidak memperbarui secara surut anotasi yang sudah ditulis. Jika Anda memerlukan warna berbeda untuk kelompok tautan berbeda pada halaman yang sama, panggil SetRGBHyperlinkColor sebelum tiap kelompok lalu kembalikan sesudahnya
Berikut dokumen minimal yang menulis tiga tautan dengan dua warna berbeda:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Biru default untuk tautan informasional
Pdf.CurrentPage.TextOut(50, 750, 0, 'Reference links:');
Pdf.CurrentPage.PrintHyperlink(50, 720, 'Product page', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
Pdf.CurrentPage.PrintHyperlink(50, 695, 'Online manual', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
// Merah untuk tautan aksi
Pdf.CurrentPage.SetRGBHyperlinkColor(clRed);
Pdf.CurrentPage.PrintHyperlink(50, 660, 'Purchase license', 'https://www.loslab.com/en-us/buy-hotpdf-fastspring.html');
Pdf.CurrentPage.SetRGBHyperlinkColor(clBlue); // kembalikan ke default
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Jebakan koordinat
HotPDF memakai titik asal kiri bawah dengan Y bertambah ke atas, dalam poin (1/72 inci). Halaman A4 berukuran 595 x 842 pt; halaman US Letter berukuran 612 x 792 pt. Y=750 duduk dekat bagian atas halaman A4, dan Y=50 akan dekat margin bawah. Siapa pun yang datang dari grafik layar atau HTML mengandaikan kebalikannya lalu menempatkan baris tautan pertama tepat di luar area yang terlihat
Persegi panjang anotasi yang dihitung PrintHyperlink memakai sistem koordinat yang sama. Jika kemudian Anda merotasi halaman, menskalakannya, atau mengubah ukuran halaman tanpa menghitung ulang nilai X/Y Anda, teks yang terlihat dan persegi panjang yang dapat diklik akan saling menjauh. Tautannya "berfungsi" dalam arti mengklik di suatu tempat dekat teks memicu URL-nya, tetapi zona panasnya tidak lagi cocok dengan apa yang dilihat pembaca. Ujilah pada ukuran halaman dan tingkat zoom yang benar-benar Anda kirimkan, bukan hanya di mesin pengembangan pada 100%
Satu kasus di mana pergeserannya pasti terjadi: jika Anda memanggil PrintHyperlink dengan koordinat yang cocok untuk halaman A4 lalu beralih ke halaman format sempit kustom tanpa menyesuaikan nilai X/Y-nya, anotasinya bisa berakhir sepenuhnya di luar halaman. Objek anotasinya tetap ditulis ke dalam PDF; sebagian besar penampil memangkasnya diam-diam, sehingga tautannya sekadar lenyap tanpa error apa pun
Teks label versus target URL
Argumen Text dan Link saling bebas. Anda bisa menggambar "Download invoice PDF" sementara targetnya adalah URL HTTPS berkualifikasi penuh dengan parameter kueri. Pemisahan itu disengaja; label yang terlihat sebaiknya terbaca manusia dan URL-nya boleh panjang atau dihasilkan secara dinamis
Yang menciptakan masalah adalah ketika labelnya berupa URL mentah itu sendiri, terutama yang panjang. Jika URL-nya membungkus secara visual ke dua baris tetapi persegi panjang anotasinya dihitung untuk string satu baris, hanya baris pertama yang dapat diklik. PrintHyperlink tidak menangani aliran multibaris; jaga labelnya cukup pendek agar muat dalam satu baris pada ukuran font dan lebar halaman saat itu, gunakan label deskriptif pendek dengan URL lengkap sebagai targetnya, atau terapkan solusi per baris yang ditunjukkan di bagian berikutnya
Untuk dokumen yang akan diarsipkan atau disebarkan tanpa koneksi internet aktif, pertimbangkan pula apakah URL-nya sendiri perlu muncul dalam bentuk tercetak di suatu tempat di badan dokumen, bukan hanya sebagai metadata anotasi. Pembaca yang mencetak PDF itu di atas kertas tidak mendapat apa-apa dari anotasi URI
Menyiasati keterbatasan multibaris
Ketika label tautan memang harus membentang lebih dari satu baris — URL panjang yang dicetak apa adanya, atau kalimat terbungkus yang seharusnya dapat diklik dari ujung ke ujung — perbaikannya adalah berhenti memperlakukannya sebagai satu tautan dan memperlakukannya sebagai satu tautan per baris. Setiap panggilan PrintHyperlink menghitung persegi panjangnya dari teks yang digambarnya, sehingga beberapa panggilan yang berbagi target Link yang sama menghasilkan beberapa anotasi berukuran tepat yang semuanya membuka URL yang sama. Pembaca tidak dapat membedakannya; setiap baris menanggapi klik
procedure PrintWrappedHyperlink(Page: THPDFPage; X, TopY, LineStep: Single;
const Lines: array of AnsiString; const Link: AnsiString);
var
I: Integer;
begin
for I := 0 to High(Lines) do
Page.PrintHyperlink(X, TopY - I * LineStep, Lines[I], Link);
end;
// Pemakaian: pecah labelnya di posisi tempat tata letak Anda membungkusnya
Pdf.CurrentPage.SetFont('Arial', [], 10);
PrintWrappedHyperlink(Pdf.CurrentPage, 50, 400, 14,
['https://www.loslab.com/en-us/pdf-library/',
'delphi-pdf-component.html'],
'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
Memecah stringnya adalah tanggung jawab Anda: patahkan di posisi yang sama tempat ia akan membungkus secara visual pada font dan lebar kolom saat itu, memakai TextWidth untuk menguji tiap baris kandidat. Alternatifnya adalah menggambar sendiri teks terbungkus itu dengan panggilan TextOut biasa lalu membentangkan satu persegi panjang AddURILink di atas tiap baris — rute yang lebih baik ketika teksnya sudah dihasilkan logika pembungkus kata Anda sendiri, yang membawa kita ke fungsi tersebut
AddURILink: area yang dapat diklik di atas apa pun yang Anda gambar
PrintHyperlink adalah pembungkus praktis: ia menggambar labelnya sendiri lalu menurunkan persegi panjangnya dari metrik label itu. AddURILink adalah separuh tingkat lebih rendahnya yang dipaparkan langsung:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Ia menulis anotasinya saja — tidak ada teks yang digambar dan tidak ada warna yang berubah. Rectangle ditafsirkan dalam ruang koordinat yang sama dengan panggilan menggambar Anda, sehingga Anda dapat memakai ulang persis nilai X/Y yang Anda berikan ke TextOut atau ke panggilan gambar. Itu menjadikannya perkakas yang tepat kapan pun konten yang terlihat sudah ada: hotspot pada citra, sel tabel, blok teks yang digambar sebelumnya, atau satu baris paragraf terbungkus seperti pada solusi di atas. Anotasinya membawa border berlebar nol, jadi tidak ada yang berubah secara kasatmata; wilayah yang dapat diklik persis persegi panjang yang Anda tentukan
Fungsi ini mengembalikan dictionary anotasi sebagai THPDFDictionaryObject. Sebagian besar pemanggil membuang hasilnya, tetapi menyimpannya memungkinkan Anda menyesuaikan entri anotasi sebelum dokumennya ditulis
Dua detail kepatuhan sudah tertanam. Dalam mode PDF/A, flag cetak anotasinya disetel sebagaimana disyaratkan standar tersebut. Di bawah PDFUACompliance, parameter Description harus berupa string tak kosong — ia menjadi entri /Contents anotasinya, yang merupakan hal yang diumumkan teknologi bantu untuk tautan itu — dan panggilannya melempar eksepsi alih-alih diam-diam memancarkan berkas yang tidak conforming. PrintHyperlink mendahului aturan itu dan tidak memasang deskripsi, jadi untuk keluaran PDF/UA gambarlah labelnya dengan TextOut lalu tempatkan anotasinya dengan AddURILink beserta deskripsi yang bermakna
Aturan keputusannya sederhana: pakai PrintHyperlink ketika tautannya berupa potongan teks pendek yang belum Anda gambar; pakai AddURILink ketika wilayah yang dapat diklik ditentukan oleh konten yang Anda gambar atau ukur sendiri
Navigasi internal dengan AddGoToLink
URL eksternal hanyalah separuh dari apa yang dilakukan anotasi tautan. Separuh lainnya adalah navigasi di dalam dokumen — daftar isi yang melompat ke bab, rujukan silang antarbagian. HotPDF memaparkannya lewat AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Tiga semantik layak dinyatakan dengan tepat, karena tak satu pun dapat ditebak dari tanda tangannya. TargetPageIndex berbasis nol: halaman pertama dokumen adalah halaman 0, sesuai dengan CurrentPageNumber. Halaman targetnya harus sudah ada ketika Anda melakukan panggilan itu; jika indeksnya di luar rentang, prosedurnya kembali tanpa menambahkan anotasi — tanpa eksepsi, tanpa tautan, tanpa peringatan. Untuk daftar isi yang menunjuk ke depan, buat semua halamannya dulu, lalu beralih kembali dan tambahkan tautannya
YPos memilih posisi vertikal pada halaman target, dalam ruang koordinat yang sama dengan panggilan menggambar Anda. Default -1 (nilai negatif apa pun) menulis koordinat destinasi null, yang memberitahu penampil untuk mempertahankan posisi vertikalnya saat itu ketika mendarat di halaman target. Berikan nilai tak negatif dan penampil menggulir sehingga posisi itu duduk di bagian atas jendela — pakai koordinat Y dari judul yang Anda tautkan. Zoom selalu dibiarkan tak berubah. Sama seperti pada AddURILink, Description harus tak kosong di bawah PDFUACompliance dan menjadi teks alternatif tautan itu
procedure BuildLinkedTOC(const FileName: string);
const
Chapters: array[0..2] of string =
('Introduction', 'Installation', 'API Reference');
var
Pdf: THotPDF;
I, Y: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc; // halaman 0 menjadi halaman daftar isi
// Buat halaman bab lebih dulu agar target tautannya ada
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // halaman 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Kembali ke halaman 0 dan gambar entri daftar isi beserta tautannya
Pdf.CurrentPageNumber := 0;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Contents');
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := 720;
for I := 0 to High(Chapters) do
begin
Pdf.CurrentPage.TextOut(70, Y, 0, Chapters[I]);
Pdf.CurrentPage.AddGoToLink(
Rect(70, Y + 14, 300, Y - 3), // menutupi entri beserta paddingnya
I + 1, // berbasis nol: bab ada di halaman 1..3
780, // mendarat dengan judul di bagian atas
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Tiap entri mendapat persegi panjang yang lebih lebar daripada teksnya sehingga seluruh barisnya menanggapi penunjuk, dan setiap tautan mendarat dengan judul bab (digambar di Y=780) berada di bagian atas jendela. Jika kemudian Anda menyisipkan satu halaman sebelum bab-babnya, setiap TargetPageIndex bergeser satu; hitunglah indeksnya dari loop pembuatan halaman Anda alih-alih menuliskannya secara kaku
Contoh pembuatan dokumen yang lengkap
Pola di bawah menunjukkan skenario yang lebih realistis: menghasilkan laporan pendek dengan bagian header, teks isi, dan baris tautan di footer, semuanya dari kode alih-alih dari formulir dengan field TEdit:
procedure GenerateProductSheet(
const FileName, ProductName, ProductURL, SupportURL: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Compression := cmFlateDecode;
Pdf.BeginDoc;
// Header
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Penampung paragraf isi
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Tautan footer
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 80, 0, 'Links:');
Pdf.CurrentPage.PrintHyperlink(50, 60, 'Product page', ProductURL);
Pdf.CurrentPage.PrintHyperlink(200, 60, 'Support', SupportURL);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Perhatikan bahwa SetFont dipanggil sebelum tiap kelompok panggilan teks. Font tidak bertahan melewati AddPage, dan jika Anda lupa menyetelnya sebelum PrintHyperlink pada halaman baru, persegi panjang anotasinya akan dihitung terhadap metrik default halaman itu, apa pun itu, yang bisa berbeda dari dugaan Anda
Di mana penanganan anotasi berbeda antarpenampil
Anotasi URI PDF didefinisikan dalam ISO 32000-1 §12.6.4.7, dan setiap penampil yang conforming semestinya mengikutinya. Dalam praktiknya, beberapa perilaku berbeda menurut penampil. Adobe Acrobat menampilkan permintaan keamanan pada klik pertama untuk URL yang tidak ada dalam daftar domain tepercaya; banyak peramban dan pembaca ringan tidak. Beberapa penampil PDF korporat di lingkungan yang terkunci menonaktifkan anotasi URI sepenuhnya lewat kebijakan, sehingga sebuah klik tidak melakukan apa-apa, tanpa error yang terlihat. Aplikasi PDF seluler berbeda-beda dalam hal membuka tautan di dalam web view aplikasinya atau menyerahkannya ke peramban sistem
Tak satu pun dari semua itu adalah bug yang dapat Anda perbaiki dari sisi pembuatan; semuanya adalah keputusan kebijakan penampil. Yang dapat Anda lakukan adalah menulis label tautan yang juga membuat URL-nya terlihat di badan dokumen, sehingga pembaca di lingkungan terbatas tetap dapat menyalin alamatnya secara manual. Anotasi adalah kemudahannya; teks adalah cadangannya
Satu detail lagi yang layak diketahui: anotasi URI PDF tidak membawa garis bawah kasatmata apa pun secara default. Garis bawah yang Anda lihat di kebanyakan penampil digambar oleh penampil itu sendiri berdasarkan tipe anotasinya, bukan oleh glyph di dalam content stream. Jika Anda membutuhkan garis bawah fisik yang selamat saat dicetak ke perender non-interaktif atau saat konversi PDF ke citra, gambarlah secara eksplisit dengan LineTo dan Stroke pada offset Y yang sesuai di bawah garis dasar teks. Itu adalah operasi menggambar terpisah, bukan sesuatu yang ditangani PrintHyperlink untuk Anda
API hyperlink yang ditunjukkan di sini adalah bagian dari HotPDF Delphi Component untuk Delphi dan C++Builder