Technischer Artikel

HotPDF Delphi Hyperlinks: PrintHyperlink Anmerkungstipps

PDF-Hyperlinks sind URI-Anmerkungen: ein Rechteck, das einen Teil der Seite abdeckt und dem Viewer beim Anklicken mitteilt, dass er eine URL öffnen soll. Die Anmerkung und der darunter liegende Text sind völlig unabhängige Objekte. HotPDFs PrintHyperlink bündelt beide in einem Aufruf, zeichnet den Text und berechnet das Anmerkungsrechteck aus den gerenderten Textmetriken. Dieser Komfort verbirgt ein Detail, das Sie verstehen sollten, bevor Sie Produktionscode schreiben

Wie PrintHyperlink funktioniert

PrintHyperlink befindet sich auf THPDFPage und nimmt vier Argumente entgegen: X- und Y-Koordinaten (in Punkten, Ursprung unten links, Y nimmt nach oben zu), die zu zeichnende Beschriftungszeichenfolge und das URL-Ziel. Intern ruft es TextOut in der aktuellen Hyperlink-Farbe auf und berechnet dann sofort das Anmerkungsrechteck aus TextWidth und TextHeight bei den aktuellen Schriftmetriken. Das bedeutet, dass Schriftart und -größe vor dem Aufruf festgelegt werden müssen und sich zwischen dem Zeichnen der Beschriftung und dem Platzieren der Anmerkung nicht ändern dürfen, da beide im selben Aufruf aufgelöst werden

Die Standardfarbe ist clBlue. SetRGBHyperlinkColor ändert sie nur für nachfolgende Aufrufe; es aktualisiert keine bereits geschriebenen Anmerkungen rückwirkend. Wenn Sie verschiedene Farben für verschiedene Linkgruppen auf derselben Seite benötigen, rufen Sie SetRGBHyperlinkColor vor jeder Gruppe auf und setzen Sie es danach zurück

Hier ist ein minimales Dokument, das drei Links mit zwei verschiedenen Farben schreibt:

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;

Die Koordinatenfalle

HotPDF verwendet einen Ursprung unten links, wobei Y nach oben wächst, in Punkten (1/72 Zoll). Eine A4-Seite ist 595 x 842 pt groß; eine US Letter-Seite ist 612 x 792 pt groß. Y=750 befindet sich in der Nähe des oberen Randes einer A4-Seite und Y=50 wäre in der Nähe des unteren Randes. Jeder, der von Bildschirmgrafiken oder HTML kommt, geht vom Gegenteil aus und platziert die erste Linkzeile direkt außerhalb des sichtbaren Bereichs

Das Anmerkungsrechteck, das PrintHyperlink berechnet, verwendet dasselbe Koordinatensystem. Wenn Sie die Seite später drehen, skalieren oder die Seitengröße ändern, ohne Ihre X/Y-Werte neu zu berechnen, driften der sichtbare Text und das anklickbare Rechteck auseinander. Der Link "funktioniert" in dem Sinne, dass ein Klick irgendwo in der Nähe des Textes die URL auslöst, aber die aktive Zone stimmt nicht mehr mit dem überein, was der Leser sieht. Testen Sie auf der tatsächlichen Seitengröße und Zoomstufe, die Sie ausliefern, und nicht nur auf dem Entwicklungsrechner bei 100%

Ein Fall, in dem die Abweichung garantiert ist: Wenn Sie PrintHyperlink mit Koordinaten aufrufen, die für eine A4-Seite geeignet sind, und dann zu einer benutzerdefinierten schmalformatigen Seite wechseln, ohne die X/Y-Werte anzupassen, kann die Anmerkung vollständig außerhalb der Seite landen. Das Anmerkungsobjekt wird weiterhin in die PDF geschrieben; die meisten Viewer schneiden es stillschweigend ab, sodass der Link einfach ohne Fehler verschwindet

Beschriftungstext versus URL-Ziel

Die Argumente Text und Link sind unabhängig. Sie können "Rechnungs-PDF herunterladen" zeichnen, während das Ziel eine vollständig qualifizierte HTTPS-URL mit Abfrageparametern ist. Diese Trennung ist beabsichtigt; die sichtbare Beschriftung sollte für Menschen lesbar sein, und die URL kann lang sein oder dynamisch generiert werden

Problematisch wird es, wenn die Beschriftung die rohe URL selbst ist, insbesondere eine lange. Wenn die URL visuell über zwei Zeilen umbrochen wird, das Anmerkungsrechteck jedoch für eine einzeilige Zeichenfolge berechnet wurde, ist nur die erste Zeile anklickbar. PrintHyperlink verarbeitet keinen mehrzeiligen Textfluss; halten Sie die Beschriftung kurz genug, um bei der aktuellen Schriftgröße und Seitenbreite in eine Zeile zu passen, oder verwenden Sie eine kurze beschreibende Beschriftung mit der vollständigen URL als Ziel

Bei Dokumenten, die archiviert oder ohne aktive Internetverbindung verteilt werden sollen, sollten Sie auch überlegen, ob die URL selbst in gedruckter Form irgendwo im Dokumentkörper erscheinen soll und nicht nur als Anmerkungsmetadaten. Ein Leser, der das PDF auf Papier ausdruckt, hat nichts von einer URI-Anmerkung

Ein vollständiges Beispiel für die Dokumentgenerierung

Das folgende Muster zeigt ein realistischeres Szenario: die Generierung eines kurzen Berichts mit einem Kopfzeilenabschnitt, Fließtext und einer Fußzeile mit Links, alles aus Code anstelle eines Formulars mit TEdit-Feldern:

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;

Beachten Sie, dass SetFont vor jeder Gruppe von Textaufrufen aufgerufen wird. Die Schriftart bleibt nicht über AddPage hinweg bestehen, und wenn Sie vergessen, sie vor PrintHyperlink auf einer neuen Seite festzulegen, wird das Anmerkungsrechteck anhand der Standardmetriken der Seite berechnet, die von Ihren Erwartungen abweichen können

Wo die Handhabung von Anmerkungen je nach Viewer variiert

PDF-URI-Anmerkungen sind in ISO 32000-1 §12.6.4.7 definiert, und jeder konforme Viewer sollte sie befolgen. In der Praxis unterscheiden sich einige Verhaltensweisen je nach Viewer. Adobe Acrobat zeigt beim ersten Klick eine Sicherheitsabfrage für URLs an, die nicht in der Liste der vertrauenswürdigen Domänen stehen; viele Browser und leichtgewichtige Reader tun dies nicht. Einige Enterprise-PDF-Viewer in stark gesicherten Umgebungen deaktivieren URI-Anmerkungen aus Richtliniengründen vollständig, sodass ein Klick nichts bewirkt und kein sichtbarer Fehler auftritt. Mobile PDF-Apps variieren darin, ob sie Links innerhalb der Webansicht der App öffnen oder an den Systembrowser übergeben

Keines davon sind Fehler, die Sie von der Generierungsseite aus beheben können; es sind Richtlinienentscheidungen des Viewers. Was Sie tun können, ist, Link-Beschriftungen zu schreiben, die die URL auch im Dokumentkörper sichtbar machen, sodass ein Leser in einer eingeschränkten Umgebung die Adresse weiterhin manuell kopieren kann. Die Anmerkung ist die Bequemlichkeit; der Text ist der Rückfall

Ein weiteres wissenswertes Detail: PDF-URI-Anmerkungen haben standardmäßig keine visuelle Unterstreichung. Die Unterstreichung, die Sie in den meisten Viewern sehen, wird vom Viewer selbst basierend auf dem Anmerkungstyp gezeichnet und nicht von einer Glyphe im Inhaltsstrom. Wenn Sie eine physische Unterstreichung benötigen, die das Drucken auf einem nicht interaktiven Renderer oder die Konvertierung von PDF in Bild überlebt, zeichnen Sie sie explizit mit LineTo und Stroke beim entsprechenden Y-Versatz unterhalb der Textgrundlinie. Das ist eine separate Zeichenoperation und nichts, was PrintHyperlink für Sie übernimmt

Die mehrzeilige Einschränkung umgehen

Wenn eine Link-Beschriftung tatsächlich über mehr als eine Zeile laufen muss - eine lange URL wortwörtlich gesetzt oder ein umbrochener Satz, der über die gesamte Länge anklickbar sein soll - besteht die Lösung darin, sie nicht als einen Link zu behandeln, sondern als einen Link pro Zeile. Jeder PrintHyperlink-Aufruf berechnet sein Rechteck aus dem Text, den er zeichnet, sodass mehrere Aufrufe mit demselben Link-Ziel mehrere korrekt dimensionierte Anmerkungen erzeugen, die alle dieselbe URL öffnen. Der Leser merkt keinen Unterschied; jede Zeile reagiert auf einen Klick

procedure PrintWrappedHyperlink(Page: THPDFPage; X, TopY, LineStep: Single;
  const Lines: array of string; const Link: AnsiString);
var
  I: Integer;
begin
  for I := 0 to High(Lines) do
    Page.PrintHyperlink(X, TopY - (I * LineStep), Lines[I], Link);
end;

Die Verantwortung für das Aufteilen des Strings liegt bei Ihnen: Trennen Sie ihn an denselben Stellen, an denen er bei der aktuellen Schrift und Spaltenbreite visuell umbrochen würde, und verwenden Sie TextWidth, um jede Kandidatenzeile zu prüfen. Die Alternative besteht darin, den umbrochenen Text selbst mit normalen TextOut-Aufrufen zu zeichnen und dann ein AddURILink-Rechteck über jede Zeile zu legen - der bessere Weg, wenn der Text bereits aus Ihrer eigenen Umbruchlogik stammt, und genau zu dieser Funktion kommen wir jetzt

AddURILink: klickbare Bereiche über allem, was Sie gezeichnet haben

PrintHyperlink ist ein Komfort-Wrapper: Er zeichnet seine eigene Beschriftung und leitet das Rechteck aus den Metriken dieser Beschriftung ab. AddURILink ist der direkt freigegebene niedrigere Teil:

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

Zwei Konformitätsdetails sind eingebaut. In PDF/A-Modi wird das Druck-Flag der Anmerkung so gesetzt, wie diese Standards es verlangen. Unter PDFUACompliance muss der Parameter Description ein nicht leerer String sein - er wird zum /Contents-Eintrag der Anmerkung, also zu dem Text, den assistive Technologien für den Link ausgeben - und der Aufruf löst eine Exception aus, statt stillschweigend eine nicht konforme Datei zu schreiben. PrintHyperlink stammt aus einer älteren Zeit und hängt keine Beschreibung an, daher sollten Sie für PDF/UA den Text mit TextOut zeichnen und die Anmerkung mit AddURILink plus einer sinnvollen Beschreibung setzen

Die Entscheidungsregel ist einfach: Verwenden Sie PrintHyperlink, wenn der Link ein kurzer Text ist, den Sie noch nicht gezeichnet haben; verwenden Sie AddURILink, wenn der anklickbare Bereich durch Inhalt bestimmt wird, den Sie selbst zeichnen oder messen

Interne Navigation mit AddGoToLink

Externe URLs sind nur die eine Hälfte von Link-Anmerkungen. Die andere Hälfte ist die Navigation innerhalb des Dokuments - ein Inhaltsverzeichnis, das zu Kapiteln springt, Querverweise zwischen Abschnitten. HotPDF stellt das über AddGoToLink bereit:

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

YPos wählt die vertikale Position auf der Zielseite im selben Koordinatenraum wie Ihre Zeichnungsaufrufe. Der Standardwert -1 (jeder negative Wert) schreibt eine Null-Zielkoordinate und teilt dem Viewer mit, seine aktuelle vertikale Position zu behalten, wenn er auf der Zielseite landet. Übergeben Sie einen nicht negativen Wert, und der Viewer scrollt so, dass diese Position oben im Fenster steht - verwenden Sie also die Y-Koordinate der Überschrift, auf die Sie verlinken. Der Zoom bleibt immer unverändert. Wie bei AddURILink muss Description unter PDFUACompliance nicht leer sein und wird zum Alternativtext des Links

Ein vollständiges Beispiel für die Dokumentgenerierung

Das folgende Muster zeigt ein realistischeres Szenario: die Generierung eines kurzen Berichts mit einem Kopfzeilenabschnitt, Fließtext und einer Fußzeile mit Links, alles aus Code anstelle eines Formulars mit TEdit-Feldern:

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;

Beachten Sie, dass SetFont vor jeder Gruppe von Textaufrufen aufgerufen wird. Die Schriftart bleibt nicht über AddPage hinweg bestehen, und wenn Sie vergessen, sie vor PrintHyperlink auf einer neuen Seite festzulegen, wird das Anmerkungsrechteck anhand der Standardmetriken der Seite berechnet, die von Ihren Erwartungen abweichen können

Die hier gezeigte Hyperlink-API ist Teil der HotPDF Component für Delphi und C++Builder