Los hipervínculos PDF son anotaciones URI: un rectángulo que cubre cierta área de la página y que, al hacer clic, le indica al visor que abra una URL. La anotación y el texto debajo de ella son objetos completamente independientes. PrintHyperlink de HotPDF empaqueta ambos en una sola llamada, dibujando el texto y calculando el rectángulo de la anotación a partir de las métricas del texto renderizado. Esa comodidad esconde un detalle que vale la pena entender antes de escribir código de producción. Tampoco es toda la historia: AddURILink coloca un área clicable sobre contenido que usted mismo dibujó, y AddGoToLink maneja la navegación interna — ambos se cubren más abajo
Cómo funciona PrintHyperlink
PrintHyperlink vive en THPDFPage y toma cuatro argumentos: las coordenadas X e Y (en puntos, origen abajo a la izquierda, Y creciendo hacia arriba), la cadena de etiqueta a dibujar y la URL de destino. Internamente llama a TextOut con el color de hipervínculo actual, y luego calcula de inmediato el rectángulo de la anotación a partir de TextWidth y TextHeight con las métricas de fuente actuales. Eso significa que la fuente y el tamaño tienen que establecerse antes de la llamada, y no deben cambiar entre dibujar la etiqueta y colocar 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 distintos grupos de enlaces en la misma página, llame a SetRGBHyperlinkColor antes de cada grupo y restablézcalo después
Este es un documento mínimo que escribe tres enlaces con dos colores distintos:
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 predeterminado
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
La trampa de las coordenadas
HotPDF usa un origen abajo a la 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 de la parte superior de una página A4, e Y=50 quedaría cerca del margen inferior. Cualquiera que venga de gráficos de pantalla o de HTML asume lo contrario y coloca la primera línea de enlace directamente fuera del área visible
El rectángulo de anotación que calcula PrintHyperlink usa el mismo sistema de coordenadas. Si luego rota la página, la escala o cambia el tamaño de página sin recalcular sus valores X/Y, el texto visible y el rectángulo clicable se separarán. El enlace "funciona" en el sentido de que hacer clic en algún lugar cerca del texto dispara la URL, pero la zona activa ya no coincide con lo que el lector ve. Pruebe con el tamaño de página y el nivel de zoom reales que va a distribuir, no solo en la computadora de desarrollo al 100%
Un caso donde la desviación está garantizada: si llama a PrintHyperlink con coordenadas apropiadas para una página A4 y luego cambia a una página personalizada de formato angosto sin ajustar los valores X/Y, la anotación puede terminar completamente fuera de la página. El objeto de anotación se escribe igual en el PDF; la mayoría de los visores lo recortan en silencio, así que el enlace simplemente desaparece sin ningún error
Texto de etiqueta frente a URL de destino
Los argumentos Text y Link son independientes. Puede dibujar "Descargar factura PDF" mientras el destino es una URL HTTPS completa con parámetros de consulta. Esa separación es deliberada; la etiqueta visible debe ser legible para humanos y la URL puede ser larga o generarse dinámicamente
Lo que crea problemas es cuando la etiqueta es la propia URL en bruto, especialmente una larga. Si la URL se envuelve visualmente en dos líneas pero el rectángulo de la anotación se calculó para una cadena de una sola línea, solo la primera línea es clicable. PrintHyperlink no maneja el flujo multilínea; mantenga la etiqueta lo bastante corta como para caber en una línea con el tamaño de fuente y el ancho de página actuales, use una etiqueta descriptiva corta con la URL completa como destino, o aplique la solución alternativa por línea que se muestra en la siguiente sección
Para documentos que se archivarán o distribuirán sin una conexión activa a internet, considere también si la propia URL debería aparecer impresa en algún lugar del cuerpo del documento, no solo como metadatos de anotación. Un lector que imprime el PDF en papel no obtiene nada de una anotación URI
Cómo sortear la limitación multilínea
Cuando la etiqueta de un enlace realmente tiene que abarcar más de una línea — una URL larga impresa textualmente, o una oración envuelta que debería ser clicable de principio a fin — la solución es dejar de tratarla como un solo 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 de tamaño correcto que abren todas la misma URL. El lector no nota la diferencia; cada línea responde a un clic
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 diseño la envuelve
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');
Dividir la cadena es responsabilidad suya: pártala en las mismas posiciones donde se envolverí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 envuelto con llamadas simples a TextOut y luego tender un rectángulo AddURILink sobre cada línea — la mejor ruta cuando el texto ya lo produce su propia lógica de ajuste de palabras, lo que nos lleva a esa función
AddURILink: áreas clicables sobre cualquier cosa que haya dibujado
PrintHyperlink es un envoltorio de conveniencia: dibuja su propia etiqueta y deriva el rectángulo de las métricas de esa etiqueta. AddURILink es la mitad de más bajo nivel expuesta directamente:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Escribe solo la anotación — no se 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 valores X/Y exactos que pasó a TextOut o a una llamada de imagen. Eso la convierte en la herramienta correcta siempre que el contenido visible ya exista: una zona activa sobre una imagen, una celda de tabla, un bloque de texto dibujado antes, o una línea de un párrafo envuelto como en la solución alternativa de arriba. La anotación lleva un borde de ancho cero, así que nada visible cambia; la región clicable es exactamente el rectángulo que usted especifica
La función devuelve el diccionario de la anotación como un THPDFDictionaryObject. La mayoría de los llamadores descartan el resultado, pero conservarlo le permite ajustar las entradas de la anotación antes de que el documento se escriba
Dos detalles de cumplimiento vienen integrados. En los modos PDF/A la bandera de impresión de la anotación se establece como exigen esos estándares. Bajo PDFUACompliance el parámetro Description debe ser una cadena no vacía — se convierte en 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, así que para salida PDF/UA dibuje la etiqueta con TextOut y coloque la anotación con AddURILink más una descripción significativa
La regla de decisión es simple: use PrintHyperlink cuando el enlace es un fragmento corto de texto que todavía no ha dibujado; use AddURILink cuando la región clicable la define contenido que usted mismo dibuja o mide
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 — una tabla de contenido que salta a los capítulos, referencias cruzadas entre secciones. HotPDF expone esto mediante AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Vale la pena enunciar con precisión tres semánticas, ya que ninguna se adivina desde la firma. TargetPageIndex empieza en cero: la primera página del documento es la página 0, en concordancia con CurrentPageNumber. La página de destino ya debe existir cuando usted hace la llamada; si el índice está fuera de rango, el procedimiento regresa sin agregar una anotación — sin excepción, sin enlace, sin advertencia. Para una tabla de contenido que apunta hacia adelante, cree primero todas las páginas, luego regrese y agregue 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, indicándole al visor que mantenga su posición vertical actual cuando aterrice en la página de destino. Pase un valor no negativo y el visor se desplaza para que esa posición quede en la parte superior de la ventana — use la coordenada Y del encabezado al que está enlazando. El zoom siempre se deja sin cambios. Igual que con AddURILink, Description debe ser no vacía bajo PDFUACompliance y se convierte en 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 página del índice
// Cree primero las páginas de capítulo para que existan los destinos de enlace
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;
// Regrese 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, // basado en 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 luego inserta una página antes de los capítulos, cada TargetPageIndex se desplaza 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 de abajo muestra un escenario más realista: generar un reporte corto con una sección de encabezado, 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;
// Encabezado
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Marcador de posición del párrafo del cuerpo
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Enlaces del 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 establecerla antes de PrintHyperlink en una página nueva, el rectángulo de la anotación se calculará contra las métricas predeterminadas que tenga la página, que pueden diferir de lo que usted espera
Dónde varía el manejo de anotaciones entre visores
Las anotaciones URI de PDF se definen en ISO 32000-1 §12.6.4.7, y todo visor conforme debería seguirlas. En la práctica, algunos comportamientos difieren según el visor. Adobe Acrobat muestra un aviso de seguridad en el primer clic para las URL que no están en la lista de dominios de confianza; muchos navegadores y lectores ligeros no lo hacen. Algunos visores PDF empresariales en entornos restringidos deshabilitan por completo las anotaciones URI por política, así que un clic no hace nada, sin ningún error visible. Las aplicaciones PDF móviles varían en si abren los enlaces dentro de la vista web de la aplicación o los delegan al navegador del sistema
Ninguno de estos es un error que pueda corregir desde el lado de la generación; son decisiones de política del visor. Lo que sí puede hacer es escribir etiquetas de enlace que hagan visible la URL también en el cuerpo del documento, para que un lector en un entorno restringido aún pueda copiar la dirección a mano. La anotación es la comodidad; el texto es el respaldo
Un detalle más que vale la pena conocer: 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 en el 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 explícitamente con LineTo y Stroke con el desplazamiento Y apropiado por debajo de la línea base del texto. Esa es una operación de dibujo aparte, no algo que PrintHyperlink haga por usted
La API de hipervínculos mostrada aquí forma parte del componente HotPDF para Delphi para Delphi y C++Builder