Los hipervínculos de PDF son anotaciones URI: un rectángulo que cubre una zona de la página y que, al pulsarlo, indica al visor que abra una URL. La anotación y el texto que hay debajo son objetos completamente independientes. PrintHyperlink de HotPDF agrupa ambos en una sola llamada: dibuja el texto y calcula el rectángulo de la anotación a partir de las métricas del texto ya compuesto. Esa comodidad esconde un detalle que conviene entender antes de escribir código de producción. Tampoco es toda la historia: AddURILink coloca un área pulsable sobre contenido que ha dibujado usted mismo, y AddGoToLink se encarga de la navegación interna; ambos se tratan más abajo
Cómo funciona PrintHyperlink
PrintHyperlink reside en THPDFPage y recibe cuatro argumentos: las coordenadas X e Y (en puntos, con origen en la esquina inferior izquierda y la Y creciendo hacia arriba), la cadena de la etiqueta que se dibuja y la URL de destino. Internamente llama a TextOut con el color de hipervínculo actual y acto seguido calcula el rectángulo de la anotación a partir de TextWidth y TextHeight con las métricas de fuente vigentes. Eso significa que la fuente y el tamaño deben fijarse antes de la llamada, y no pueden cambiar entre el dibujado de la etiqueta y la colocación de la anotación, porque ambas cosas se resuelven en la misma llamada
El color predeterminado es clBlue. SetRGBHyperlinkColor lo cambia solo para las llamadas posteriores; no actualiza retroactivamente las anotaciones ya escritas. Si necesita colores distintos para grupos de enlaces distintos en la misma página, llame a SetRGBHyperlinkColor antes de cada grupo y restáurelo después
A continuación, un documento mínimo que escribe tres enlaces con dos colores diferentes:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Azul predeterminado para los enlaces informativos
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');
// Rojo para el enlace de acción
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); // restaura el valor predeterminado
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
La trampa de las coordenadas
HotPDF utiliza un origen en la esquina inferior izquierda con la Y creciendo hacia arriba, en puntos (1/72 de pulgada). Una página A4 mide 595 x 842 pt; una página US Letter mide 612 x 792 pt. Y=750 queda cerca del borde superior de una página A4, e Y=50 quedaría cerca del margen inferior. Quien llega desde los gráficos de pantalla o desde HTML supone lo contrario y coloca la primera línea de enlaces justo fuera del área visible
El rectángulo de anotación que calcula PrintHyperlink usa el mismo sistema de coordenadas. Si más adelante gira la página, la escala o cambia su tamaño sin recalcular los valores X/Y, el texto visible y el rectángulo pulsable se separarán. El enlace «funciona» en el sentido de que pulsar en algún punto cercano al texto dispara la URL, pero la zona activa ya no coincide con lo que ve el lector. Pruebe con el tamaño de página y el nivel de zoom reales que va a distribuir, no solo en la máquina de desarrollo al 100%
Un caso en el que la desviación está garantizada: si llama a PrintHyperlink con coordenadas pensadas para una página A4 y después cambia a una página estrecha de formato personalizado sin ajustar los valores X/Y, la anotación puede acabar completamente fuera de la página. El objeto de anotación se sigue escribiendo en el PDF; la mayoría de los visores lo recortan en silencio, así que el enlace desaparece sin más y sin error alguno
Texto de la etiqueta frente a URL de destino
Los argumentos Text y Link son independientes. Puede dibujar «Descargar factura en PDF» mientras el destino es una URL HTTPS completa con parámetros de consulta. Esa separación es deliberada: la etiqueta visible debe resultar legible para una persona y la URL puede ser larga o generarse de forma dinámica
Los problemas llegan cuando la etiqueta es la propia URL en bruto, sobre todo si es larga. Si la URL se parte visualmente en dos líneas pero el rectángulo de anotación se calculó para una cadena de una sola línea, solo la primera línea es pulsable. PrintHyperlink no gestiona el flujo multilínea; mantenga la etiqueta lo bastante corta para que quepa en una línea con el tamaño de fuente y el ancho de página actuales, use una etiqueta descriptiva breve con la URL completa como destino, o aplique el apaño por líneas que se muestra en la sección siguiente
En documentos que se van a archivar o distribuir sin conexión activa a internet, valore además si la propia URL debería aparecer impresa en algún punto del cuerpo del documento y no solo como metadato de la anotación. A quien imprima el PDF en papel, una anotación URI no le aporta nada
Cómo sortear la limitación multilínea
Cuando una etiqueta de enlace tiene que ocupar de verdad más de una línea —una URL larga impresa literalmente, o una frase partida que debe ser pulsable de principio a fin—, la solución es dejar de tratarla como un enlace y tratarla como un enlace por línea. Cada llamada a PrintHyperlink calcula su rectángulo a partir del texto que dibuja, así que varias llamadas que comparten el mismo destino Link producen varias anotaciones del tamaño correcto que abren todas la misma URL. El lector no nota la diferencia; cada línea responde a la pulsación
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;
// Uso: parta la etiqueta en las posiciones donde su maquetación la divide
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');
Partir la cadena es responsabilidad suya: divídala en las mismas posiciones donde se partiría visualmente con la fuente y el ancho de columna actuales, usando TextWidth para probar cada línea candidata. La alternativa es dibujar usted mismo el texto partido con llamadas normales a TextOut y colocar después un rectángulo AddURILink sobre cada línea; es la mejor vía cuando el texto ya lo produce su propia lógica de ajuste de línea, lo que nos lleva a esa función
AddURILink: áreas pulsables sobre cualquier cosa que haya dibujado
PrintHyperlink es un envoltorio de comodidad: dibuja su propia etiqueta y deriva el rectángulo de las métricas de esa etiqueta. AddURILink es la mitad de bajo nivel expuesta directamente:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Escribe solo la anotación: no dibuja texto ni cambia ningún color. El Rectangle se interpreta en el mismo espacio de coordenadas que sus llamadas de dibujo, así que puede reutilizar los mismos valores X/Y que pasó a TextOut o a una llamada de imagen. Eso lo convierte en la herramienta adecuada siempre que el contenido visible ya existe: una zona activa sobre una imagen, una celda de tabla, un bloque de texto dibujado antes o una línea de un párrafo partido como en el apaño anterior. La anotación lleva un borde de ancho cero, así que nada visible cambia; la región pulsable es exactamente el rectángulo que indique
La función devuelve el diccionario de la anotación como un THPDFDictionaryObject. La mayoría de quienes la llaman descartan el resultado, pero conservarlo le permite ajustar las entradas de la anotación antes de que se escriba el documento
Hay dos detalles de conformidad ya integrados. En los modos PDF/A se activa el indicador de impresión de la anotación tal como exigen esos estándares. Bajo PDFUACompliance, el parámetro Description debe ser una cadena no vacía —pasa a ser la entrada /Contents de la anotación, que es lo que la tecnología de asistencia anuncia para el enlace— y la llamada lanza una excepción en lugar de emitir en silencio un archivo no conforme. PrintHyperlink es anterior a esa regla y no adjunta descripción alguna, así que para salida PDF/UA dibuje la etiqueta con TextOut y coloque la anotación con AddURILink más una descripción con sentido
La regla de decisión es sencilla: use PrintHyperlink cuando el enlace sea un texto corto que aún no ha dibujado; use AddURILink cuando la región pulsable la defina contenido que dibuja o mide usted mismo
Navegación interna con AddGoToLink
Las URL externas son solo la mitad de lo que hacen las anotaciones de enlace. La otra mitad es la navegación dentro del documento: un índice que salta a los capítulos, referencias cruzadas entre secciones. HotPDF lo expone mediante AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Conviene enunciar con precisión tres detalles semánticos, porque ninguno se adivina a partir de la firma. TargetPageIndex empieza en cero: la primera página del documento es la página 0, igual que en CurrentPageNumber. La página de destino debe existir ya cuando hace la llamada; si el índice queda fuera de rango, el procedimiento retorna sin añadir anotación: ni excepción, ni enlace, ni aviso. Para un índice que apunta hacia delante, cree primero todas las páginas y vuelva después a añadir los enlaces
YPos selecciona la posición vertical en la página de destino, en el mismo espacio de coordenadas que sus llamadas de dibujo. El valor predeterminado de -1 (cualquier valor negativo) escribe una coordenada de destino nula, con lo que el visor mantiene su posición vertical actual al llegar a la página de destino. Pase un valor no negativo y el visor desplazará el documento para que esa posición quede en la parte superior de la ventana; use la coordenada Y del encabezado al que enlaza. El zoom se deja siempre sin cambios. Igual que con AddURILink, Description debe ser no vacía bajo PDFUACompliance y pasa a ser el texto alternativo del enlace
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; // la página 0 pasa a ser la del índice
// Cree antes las páginas de capítulo para que existan los destinos
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // páginas 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Vuelva a la página 0 y dibuje las entradas del índice con sus enlaces
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), // cubre la entrada con margen
I + 1, // base cero: los capítulos son las páginas 1..3
780, // aterriza con el encabezado arriba
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Cada entrada recibe un rectángulo más ancho que el texto para que toda la fila responda al puntero, y cada enlace aterriza con el encabezado del capítulo (dibujado en Y=780) en la parte superior de la ventana. Si más adelante inserta una página antes de los capítulos, todos los TargetPageIndex se desplazan en uno; calcule los índices a partir de su bucle de creación de páginas en lugar de codificarlos a mano
Un ejemplo completo de generación de documentos
El patrón siguiente muestra un escenario más realista: generar un informe breve con una sección de cabecera, texto de cuerpo y una fila de enlaces al pie, todo desde código y no desde un formulario con campos 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;
// Cabecera
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Marcador de párrafo de cuerpo
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Enlaces de pie
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;
Observe que SetFont se llama antes de cada grupo de llamadas de texto. La fuente no persiste a través de AddPage, y si olvida fijarla antes de PrintHyperlink en una página nueva, el rectángulo de anotación se calculará con las métricas predeterminadas de esa página, que pueden diferir de lo que espera
Dónde varía el tratamiento de anotaciones entre visores
Las anotaciones URI de PDF están definidas en ISO 32000-1 §12.6.4.7, y todo visor conforme debería seguirlas. En la práctica, unos cuantos comportamientos difieren según el visor. Adobe Acrobat muestra un aviso de seguridad en la primera pulsación para las URL que no figuran en la lista de dominios de confianza; muchos navegadores y lectores ligeros no lo hacen. Algunos visores PDF corporativos en entornos restringidos desactivan por completo las anotaciones URI por política, de modo que la pulsación no hace nada y no aparece ningún error. Las aplicaciones PDF móviles varían en si abren los enlaces dentro de la vista web de la propia aplicación o los ceden al navegador del sistema
Ninguno de estos casos es un fallo que pueda corregir desde el lado de la generación; son decisiones de política del visor. Lo que sí puede hacer es redactar etiquetas de enlace que dejen la URL también visible en el cuerpo del documento, para que un lector en un entorno restringido pueda copiar la dirección a mano. La anotación es la comodidad; el texto es el plan B
Un detalle más que conviene saber: las anotaciones URI de PDF no llevan ningún subrayado visual de forma predeterminada. El subrayado que ve en la mayoría de los visores lo dibuja el propio visor según el tipo de anotación, no un glifo del flujo de contenido. Si necesita un subrayado físico que sobreviva a la impresión en un renderizador no interactivo o a la conversión de PDF a imagen, dibújelo de forma explícita con LineTo y Stroke en el desplazamiento Y adecuado por debajo de la línea base del texto. Es una operación de dibujo aparte, no algo de lo que se ocupe PrintHyperlink por usted
La API de hipervínculos que se muestra aquí forma parte del HotPDF Delphi Component para Delphi y C++Builder