Teknik Makale

HotPDF Delphi Köprüleri: PrintHyperlink Annotation İpuçları

PDF köprüleri URI annotation'larıdır: bir sayfa alanının bir kısmını kaplayan ve tıklandığında görüntüleyiciye bir URL açmasını söyleyen bir dikdörtgen. Annotation ile onun altındaki metin tamamen bağımsız nesnelerdir. HotPDF'in PrintHyperlink metodu her ikisini de tek bir çağrıda paketler; metni çizer ve annotation dikdörtgenini işlenmiş metin metriklerinden hesaplar. Bu kolaylık, üretim kodu yazmadan önce anlaşılmaya değer bir ayrıntıyı gizler. Ayrıca hikâyenin tamamı da bu değildir: AddURILink, kendinizin çizdiği içeriğin üzerine tıklanabilir bir alan yerleştirir ve AddGoToLink belge içi gezinmeyi ele alır — ikisi de aşağıda ele alınmaktadır

PrintHyperlink nasıl çalışır

PrintHyperlink, THPDFPage üzerinde yer alır ve dört argüman alır: X ve Y koordinatları (punto cinsinden, sol alt köşe başlangıç noktası, Y yukarı doğru artar), çizilecek etiket dizesi ve URL hedefi. Dahili olarak geçerli köprü renginde TextOut çağırır, ardından annotation dikdörtgenini hemen geçerli yazı tipi metriklerindeki TextWidth ve TextHeight değerlerinden hesaplar. Bu, yazı tipi ve boyutunun çağrıdan önce ayarlanmış olması gerektiği ve etiketi çizmek ile annotation'ı yerleştirmek arasında değişmemeleri gerektiği anlamına gelir, çünkü ikisi de aynı çağrıda çözümlenir

Varsayılan renk clBlue'dur. SetRGBHyperlinkColor onu yalnızca sonraki çağrılar için değiştirir; halihazırda yazılmış annotation'ları geriye dönük olarak güncellemez. Aynı sayfada farklı bağlantı grupları için farklı renklere ihtiyacınız varsa, her gruptan önce SetRGBHyperlinkColor çağırın ve sonrasında sıfırlayın

İşte iki farklı renkte üç bağlantı yazan minimal bir belge:

procedure CreateLinkedReport(const FileName: string);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.BeginDoc;

    Pdf.CurrentPage.SetFont('Arial', [], 11);

    // Default blue for informational links
    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');

    // Red for the action link
    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);  // restore default

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Koordinat tuzağı

HotPDF, punto (1/72 inç) cinsinden Y'nin yukarı doğru büyüdüğü sol alt başlangıç noktasını kullanır. Bir A4 sayfası 595 x 842 pt'dir; bir US Letter sayfası ise 612 x 792 pt'dir. Y=750 bir A4 sayfasının üst kısmına yakın oturur ve Y=50 alt kenar boşluğuna yakın olurdu. Ekran grafiklerinden veya HTML'den gelen biri bunun tersini varsayar ve ilk bağlantı satırını doğrudan görünür alanın dışına yerleştirir

PrintHyperlink'in hesapladığı annotation dikdörtgeni aynı koordinat sistemini kullanır. Daha sonra sayfayı döndürür, ölçeklendirir veya X/Y değerlerinizi yeniden hesaplamadan sayfa boyutunu değiştirirseniz, görünür metin ile tıklanabilir dikdörtgen birbirinden ayrılır. Bağlantı, metnin yakınında bir yere tıklamanın URL'yi tetiklemesi anlamında "çalışır", ancak etkin bölge artık okuyucunun gördüğüyle eşleşmez. Yalnızca %100'de geliştirme makinesinde değil, gönderdiğiniz gerçek sayfa boyutu ve yakınlaştırma seviyesinde test edin

Sürüklenmenin garanti olduğu bir durum: PrintHyperlink'i bir A4 sayfasına uygun koordinatlarla çağırıp ardından X/Y değerlerini ayarlamadan özel dar formatlı bir sayfaya geçerseniz, annotation tamamen sayfanın dışında kalabilir. Annotation nesnesi yine de PDF'e yazılır; çoğu görüntüleyici onu sessizce kırpar, bu yüzden bağlantı herhangi bir hata olmadan basitçe kaybolur

Etiket metni ile URL hedefi

Text ve Link argümanları bağımsızdır. Hedef, sorgu parametreleri olan tam nitelikli bir HTTPS URL'si iken "Download invoice PDF" çizebilirsiniz. Bu ayrım bilinçlidir; görünür etiket insan tarafından okunabilir olmalıdır ve URL uzun veya dinamik olarak üretilmiş olabilir

Sorun yaratan şey, etiketin özellikle uzun olduğunda ham URL'nin kendisi olmasıdır. URL görsel olarak iki satıra sarıyorsa ancak annotation dikdörtgeni tek satırlık bir dize için hesaplanmışsa, yalnızca ilk satır tıklanabilir olur. PrintHyperlink çok satırlı akışı ele almaz; etiketi geçerli yazı tipi boyutunda ve sayfa genişliğinde tek bir satıra sığacak kadar kısa tutun, tam URL'yi hedef olarak alan kısa açıklayıcı bir etiket kullanın veya bir sonraki bölümde gösterilen satır başına çözümü uygulayın

Etkin bir internet bağlantısı olmadan arşivlenecek veya dağıtılacak belgeler için, URL'nin kendisinin yalnızca annotation meta verisi olarak değil, belgenin gövdesinde bir yerde basılı biçimde görünmesi gerekip gerekmediğini de düşünün. PDF'i kâğıda yazdıran bir okuyucu, bir URI annotation'ından hiçbir şey elde etmez

Çok satır sınırlamasını aşmak

Bir bağlantı etiketi gerçekten birden fazla satıra yayılmak zorunda kaldığında — birebir basılan uzun bir URL veya baştan sona tıklanabilir olması gereken sarılmış bir cümle — çözüm, onu tek bir bağlantı olarak ele almayı bırakıp satır başına bir bağlantı olarak ele almaktır. Her PrintHyperlink çağrısı, dikdörtgenini çizdiği metinden hesaplar, bu nedenle aynı Link hedefini paylaşan birkaç çağrı, hepsi aynı URL'yi açan, doğru boyutlandırılmış birkaç annotation üretir. Okuyucu farkı anlayamaz; her satır bir tıklamaya yanıt verir

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;

// Usage: break the label at the positions where your layout wraps it
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');

Dizeyi bölmek sizin sorumluluğunuzdadır: her aday satırı test etmek için TextWidth kullanarak, geçerli yazı tipi ve sütun genişliğinde görsel olarak sarılacağı aynı konumlarda bölün. Alternatif, sarılmış metni sade TextOut çağrılarıyla kendiniz çizmek ve ardından her satırın üzerine bir AddURILink dikdörtgeni sermektir — metin zaten kendi kelime sarma mantığınızca üretildiğinde daha iyi bir yol, ki bu da bizi o fonksiyona getiriyor

AddURILink: çizdiğiniz her şeyin üzerine tıklanabilir alanlar

PrintHyperlink bir kolaylık sarmalayıcısıdır: kendi etiketini çizer ve dikdörtgeni o etiketin metriklerinden türetir. AddURILink, doğrudan açığa çıkarılan daha alt seviye yarıdır:

function AddURILink(Rectangle: TRect; const URL: AnsiString;
  const Description: AnsiString = ''): THPDFDictionaryObject;

Yalnızca annotation'ı yazar — hiçbir metin çizilmez ve hiçbir renk değişmez. Rectangle, çizim çağrılarınızla aynı koordinat uzayında yorumlanır, bu nedenle TextOut'a veya bir resim çağrısına geçtiğiniz tam X/Y değerlerini yeniden kullanabilirsiniz. Bu, görünür içerik zaten var olduğunda onu doğru araç yapar: bir resim etkin noktası, bir tablo hücresi, daha önce çizilmiş bir metin bloğu veya yukarıdaki çözümdeki gibi sarılmış bir paragrafın bir satırı. Annotation, sıfır genişlikte bir kenarlık taşır, bu yüzden görünür hiçbir şey değişmez; tıklanabilir bölge tam olarak belirttiğiniz dikdörtgendir

Fonksiyon, annotation sözlüğünü bir THPDFDictionaryObject olarak döndürür. Çoğu çağıran sonucu atar, ancak onu tutmak, belge yazılmadan önce annotation'ın girişlerini ayarlamanıza olanak tanır

Yerleşik iki uyumluluk ayrıntısı vardır. PDF/A modlarında, annotation'ın print bayrağı bu standartların gerektirdiği şekilde ayarlanır. PDFUACompliance altında Description parametresi boş olmayan bir dize olmalıdır — bu, yardımcı teknolojinin bağlantı için duyurduğu şey olan annotation'ın /Contents girişi olur — ve çağrı, uyumsuz bir dosyayı sessizce yaymak yerine bir exception fırlatır. PrintHyperlink bu kuraldan öncedir ve hiçbir açıklama eklemez, bu yüzden PDF/UA çıktısı için etiketi TextOut ile çizin ve annotation'ı anlamlı bir açıklamayla birlikte AddURILink ile yerleştirin

Karar kuralı basittir: bağlantı henüz çizmediğiniz kısa bir metin parçasıysa PrintHyperlink'i kullanın; tıklanabilir bölge, kendinizin çizdiği veya ölçtüğü içerik tarafından tanımlanıyorsa AddURILink'i kullanın

AddGoToLink ile belge içi gezinme

Harici URL'ler, bağlantı annotation'larının yaptığının yalnızca yarısıdır. Diğer yarısı belge içindeki gezinmedir — bölümlere atlayan bir içindekiler tablosu, bölümler arası çapraz referanslar. HotPDF bunu AddGoToLink aracılığıyla açığa çıkarır:

procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
  YPos: Single = -1; const Description: AnsiString = '');

Üç anlam kesin olarak belirtilmeye değerdir, çünkü hiçbiri imzadan tahmin edilemez. TargetPageIndex sıfır tabanlıdır: belgenin ilk sayfası sayfa 0'dır ve CurrentPageNumber ile eşleşir. Çağrıyı yaptığınızda hedef sayfa zaten var olmalıdır; dizin aralık dışındaysa, prosedür bir annotation eklemeden döner — exception yok, bağlantı yok, uyarı yok. İleriye işaret eden bir içindekiler tablosu için önce tüm sayfaları oluşturun, ardından geri dönüp bağlantıları ekleyin

YPos, hedef sayfadaki dikey konumu, çizim çağrılarınızla aynı koordinat uzayında seçer. Varsayılan -1 (herhangi bir negatif değer), boş bir hedef koordinatı yazar ve görüntüleyiciye hedef sayfaya vardığında geçerli dikey konumunu korumasını söyler. Negatif olmayan bir değer geçin ve görüntüleyici, o konum pencerenin üstünde oturacak şekilde kaydırır — bağladığınız başlığın Y koordinatını kullanın. Yakınlaştırma her zaman değişmeden bırakılır. AddURILink'te olduğu gibi, PDFUACompliance altında Description boş olmamalıdır ve bağlantının alternatif metni olur

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;                        // page 0 becomes the TOC page

    // Create the chapter pages first so the link targets exist
    for I := 0 to High(Chapters) do
    begin
      Pdf.AddPage;                       // pages 1..3
      Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
      Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
    end;

    // Switch back to page 0 and draw the TOC entries with their links
    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),    // covers the entry with padding
        I + 1,                           // zero-based: chapters are pages 1..3
        780,                             // land with the heading at the top
        AnsiString('Go to ' + Chapters[I]));
      Y := Y - 25;
    end;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Her giriş, tüm satırın işaretçiye yanıt vermesi için metinden daha geniş bir dikdörtgen alır ve her bağlantı, bölüm başlığı (Y=780'de çizilmiş) pencerenin üstünde olacak şekilde konumlanır. Daha sonra bölümlerin önüne bir sayfa eklerseniz, her TargetPageIndex bir kayar; dizinleri sabit kodlamak yerine sayfa oluşturma döngünüzden hesaplayın

Eksiksiz bir belge üretim örneği

Aşağıdaki desen, daha gerçekçi bir senaryo gösterir: bir başlık bölümü, gövde metni ve bir alt bilgi bağlantı satırı ile kısa bir raporu, TEdit alanları olan bir formdan değil, tamamen koddan üretmek:

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));

    // Body paragraph placeholder
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');

    // Footer links
    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;

SetFont'un her metin çağrısı grubundan önce çağrıldığına dikkat edin. Yazı tipi AddPage boyunca kalıcı olmaz ve yeni bir sayfada PrintHyperlink'ten önce ayarlamayı unutursanız, annotation dikdörtgeni sayfanın varsayılan metrikleri her ne ise ona göre hesaplanır ki bu beklediğinizden farklı olabilir

Annotation işleyişinin görüntüleyiciler arasında değiştiği yerler

PDF URI annotation'ları ISO 32000-1 §12.6.4.7'de tanımlanmıştır ve uyumlu her görüntüleyici bunları izlemelidir. Uygulamada, birkaç davranış görüntüleyiciye göre değişir. Adobe Acrobat, güvenilen alan adları listesinde olmayan URL'ler için ilk tıklamada bir güvenlik istemi gösterir; birçok tarayıcı ve hafif okuyucu göstermez. Kilitli ortamlardaki bazı kurumsal PDF görüntüleyiciler, URI annotation'larını politika gereği tamamen devre dışı bırakır, bu yüzden bir tıklama görünür bir hata olmadan hiçbir şey yapmaz. Mobil PDF uygulamaları, bağlantıları uygulamanın web görünümü içinde mi yoksa sistem tarayıcısına devredip mi açacağı konusunda değişkenlik gösterir

Bunların hiçbiri üretim tarafından düzeltebileceğiniz hatalar değildir; bunlar görüntüleyici politika kararlarıdır. Yapabileceğiniz şey, URL'yi belge gövdesinde de görünür kılan bağlantı etiketleri yazmaktır, böylece kısıtlı bir ortamdaki bir okuyucu yine de adresi manuel olarak kopyalayabilir. Annotation kolaylıktır; metin ise yedektir

Bilmeye değer bir başka ayrıntı: PDF URI annotation'ları varsayılan olarak herhangi bir görsel alt çizgi taşımaz. Çoğu görüntüleyicide gördüğünüz alt çizgi, içerik akışındaki bir glif tarafından değil, annotation türüne dayanarak görüntüleyicinin kendisi tarafından çizilir. Etkileşimli olmayan bir işleyiciye yazdırmaya veya PDF'ten resme dönüştürmeye dayanan fiziksel bir alt çizgiye ihtiyacınız varsa, onu metin taban çizgisinin altındaki uygun Y ofsetinde LineTo ve Stroke ile açıkça çizin. Bu ayrı bir çizim işlemidir, PrintHyperlink'in sizin için ele aldığı bir şey değildir

Burada gösterilen köprü API'si, Delphi ve C++Builder için HotPDF Bileşeninin bir parçasıdır