Technical Article

Anotaciones PDF en Delphi con HotPDF: tipos y rectángulos

Una anotación no es contenido de página. Cuando se llama a TextOut o se dibuja un rectángulo, las marcas pasan a formar parte del flujo de contenido de la página, integradas en los bytes que pinta un renderizador. Una anotación es un diccionario independiente que cuelga de la página a través de su array /Annots, con su propio rectángulo, su propia apariencia y su propio ciclo de vida. Un lector puede abrirla, moverla, ocultarla o eliminarla sin tocar ni un solo glifo de la página subyacente. Esa separación es la razón de ser de las anotaciones, y también es el origen de las dos cosas que sorprenden a la gente en primer lugar: dónde aterriza una anotación y qué aspecto tiene una vez que un visor concreto la maneja.

HotPDF expone los subtipos de anotación de ISO 32000 a través de una familia de llamadas AddXxxAnnotation sobre el objeto de página. Todas comparten la misma forma: un rectángulo que fija la anotación en la página en el espacio de usuario PDF, algún contenido (texto, nombre de sello, par de puntos) y un color. Si se define bien el rectángulo, la mayor parte del trabajo está hecha. El resto consiste en saber qué subtipos llevan su propia apariencia y cuáles dependen del visor para renderizarlos.

A PDF page produced by HotPDF showing text note icons, free text boxes, square and line markups, and approval stamps placed across the page
Una página con varios subtipos de anotación a la vez: notas de texto, texto libre, marcas geométricas y sellos

El rectángulo es la anotación, no el texto

Cada llamada de anotación recibe un TRect, y ese rectángulo significa algo distinto de las coordenadas que se pasan a TextOut. Para una nota de texto es el área activa en la que se puede hacer clic, la pequeña región donde se sitúa el icono de la nota y donde un clic abre el comentario. Para un cuadrado o un cuadro de texto libre es la extensión visible de la marca. Para un sello es el cuadro en el que se escala la imagen del sello. Los números son puntos del espacio de usuario PDF, medidos desde la esquina inferior izquierda de la página con Y creciendo hacia arriba, la misma convención que usa el resto de HotPDF.

Una nota de texto es el subtipo más ligero. Se le pasa el texto del cuerpo, un rectángulo para el icono, un indicador de si se abre por defecto, un nombre de icono y un color.

Pdf.CurrentPage.AddTextAnnotation(
  'Reviewer: confirm the totals on this line before sign-off.',
  Rect(120, 700, 140, 720),   // icon hotspot, ~20pt square
  False,                      // closed until the reader clicks it
  taComment,                  // bubble icon
  clBlue);

El rectángulo aquí es deliberadamente pequeño, de unos veinte puntos por lado, porque una nota de texto es solo un icono hasta que alguien hace clic en él. Si se hace el rectángulo grande, no se obtiene una nota grande, sino un área activa sobredimensionada con el icono fijado en una esquina. El indicador Open controla si el popup está visible cuando se carga el documento. Si se establecen varias notas a True, se apilan unas sobre otras y sobre el contenido, así que reservad eso para la única nota que realmente queréis que el lector vea de inmediato.

El nombre del icono procede de THPDFTextAnnotationType, que se corresponde con los iconos de nota estándar: taComment, taKey, taNote, taHelp, taParagraph, taNewParagraph y taInsert. El icono es lo único que cambia el tipo. No altera el comportamiento, y conviene saber que no todos los visores dibujan los siete; los que funcionan de forma fiable tanto en lectores antiguos como nuevos son taComment, taNote y taHelp.

El texto libre escribe en la página, pero sigue siendo una anotación

Una anotación de texto libre parece contenido porque el texto es visible sin hacer clic, situado en su rectángulo como un pie de foto. Sigue siendo una anotación, con toda la separabilidad que eso implica, que es exactamente lo que se quiere para un sello de revisión o una etiqueta de borrador que alguien debería poder eliminar más tarde. La firma intercambia el icono y el indicador de apertura por un valor de justificación.

Pdf.CurrentPage.AddFreeTextAnnotation(
  'DRAFT - not for distribution',
  Rect(200, 210, 400, 235),   // the box the text is laid into
  ftCenter,                   // ftLeftJust / ftCenter / ftRightJust
  clRed);

Aquí el rectángulo importa más que en una nota de texto, porque el texto se ajusta y alinea dentro de él. Si el cuadro es demasiado corto, el texto se recorta en el borde inferior; si es demasiado estrecho, se ajusta en lugares no previstos. La justificación procede de THPDFFreeTextAnnotationJust y solo tiene los tres valores. Como el texto libre es una anotación de marcado, un lector que abra el fichero en un editor puede seleccionarlo, moverlo o eliminarlo como una unidad, lo que determina si se recurre al texto libre o simplemente se dibujan las palabras con TextOut. Si la etiqueta debe ser permanente, dibujadla. Si es editorial y está pensada para eliminarse, convertidla en una anotación.

Marcas geométricas y de línea para señalar elementos

Los cuadrados, los círculos y las líneas son las marcas que se usan para señalar una región en lugar de describirla con palabras. AddCircleSquareAnnotation cubre las dos formas de cuadro mediante un THPDFCSAnnotationType de csCircle o csSquare, con el rectángulo proporcionando los límites de la forma.

// A box drawn around a figure that needs attention
Pdf.CurrentPage.AddCircleSquareAnnotation(
  'Check this region against the source data',
  Rect(50, 300, 120, 360),
  csSquare,
  clGreen);

// A line, given two points rather than a rectangle
var
  StartPt, EndPt: THPDFCurrPoint;
begin
  StartPt.X := 130; StartPt.Y := 360;
  EndPt.X   := 250; EndPt.Y   := 320;
  Pdf.CurrentPage.AddLineAnnotation(
    'Points from the note to the figure',
    StartPt, EndPt,
    clBlue);
end;

Observad que la anotación de línea rompe el patrón del rectángulo: toma dos registros THPDFCurrPoint, un inicio y un final, porque una línea se define por sus extremos, no por un cuadro delimitador. El color establece el trazo. Si queréis cabezas de flecha, HotPDF tiene sobrecargas de AddLineAnnotation que aceptan estilos de terminación de línea, pero la forma de tres argumentos simples dibuja una línea sin adornos, que es generalmente lo que necesita una llamada.

Los subtipos de marcado de texto actúan sobre una región que ya habéis compuesto. AddHighlightAnnotation toma un rectángulo, contenido opcional y un color que por defecto es amarillo, y tinta el área como lo haría un rotulador fluorescente. Está pensado para situarse sobre texto real, por lo que el rectángulo debería coincidir con los límites de las palabras que dibujasteis, lo que significa que normalmente se calcula a partir de las mismas coordenadas que se pasaron a TextOut en lugar de estimarlas.

Los sellos dependen del visor para renderizarse

Una anotación de sello es la que más probabilidades tiene de verse diferente de un lector a otro, y vale la pena entender el motivo. AddStampAnnotation nombra un sello estándar mediante THPDFStampAnnotationType, con valores como satApproved, satConfidential, satFinal, satDraft y satForComment.

Pdf.CurrentPage.AddStampAnnotation(
  'Approved for release on review',
  Rect(50, 400, 200, 440),
  satApproved,
  clGreen);

El nombre del sello es una solicitud. PDF define el conjunto de nombres de sellos estándar pero no la imagen detrás de ellos, por lo que cada visor incluye su propia representación de «APPROVED» o «CONFIDENTIAL», y algunos no muestran nada para nombres que no reconocen. El rectángulo controla el cuadro en el que se escala la imagen, y el color es una sugerencia que el visor puede respetar o no. Si un sello debe verse idéntico en todas partes, la vía fiable no es un sello estándar: dibujad la marca vosotros mismos con TextOut y las llamadas de dibujo, o colocadla como una anotación de texto libre cuya apariencia controlais vosotros. Recurrид al sello estándar cuando queráis el aspecto familiar del visor y podáis tolerar la variación.

Los archivos adjuntos siguen la misma forma de rectángulo más contenido. AddFileAttachmentAnnotation toma la descripción, la ruta del fichero a incrustar, un rectángulo para el icono del clip y un color. El fichero va dentro del PDF, y el icono es el punto de acceso que un lector usa para extraerlo.

Cómo se diferencian las anotaciones de los campos AcroForm

La confusión que más tiempo cuesta es tratar una anotación como si fuera un campo de formulario. Ambos se adjuntan a la página a través de /Annots, y un campo de formulario es de hecho un subtipo de anotación especial (un widget), lo que explica por qué parecen relacionados. No son intercambiables. Un campo de formulario contiene un valor, tiene un nombre, participa en el orden de tabulación y puede enviarse, restablecerse o programarse; esos se crean con las llamadas AddTextField, AddCheckBox y AddPushButton, no con las llamadas de anotación de esta página. Una anotación de marcado contiene un comentario o una forma, no tiene valor que enviar, y es la herramienta equivocada en el momento en que se necesita recopilar datos.

La prueba práctica es sencilla. Si se espera que un usuario escriba, elija o haga clic y que el documento lo recuerde, se necesita un campo AcroForm. Si se deja una nota, se marca una región o se estampa un estado que viaja con el fichero pero no es un dato, se necesita una anotación. Mezclarlos produce documentos que tienen buen aspecto pero se comportan mal: un «campo» que nadie puede rellenar, o un comentario que desaparece cuando se restablece un formulario. El lado interactivo, con tipos de campo, validación y acciones de envío, es un tema aparte tratado en el recorrido por campos y acciones AcroForm.

Componer una página

Las piezas se componen como el resto de HotPDF. Estableced las propiedades del documento, llamad a BeginDoc, dibujad el contenido de página que necesitéis con las llamadas de texto y gráficos, añadid anotaciones encima y cerrad con EndDoc. Las anotaciones se adjuntan a CurrentPage, por lo que después de un AddPage aterrizan en la página nueva, y una nota destinada a la página uno aparecerá silenciosamente en la página dos si se añade después del salto.

Pdf := THotPDF.Create(nil);
try
  Pdf.FileName := 'annotated.pdf';
  Pdf.Compression := cmFlateDecode;
  Pdf.FontEmbedding := True;
  Pdf.BeginDoc;

  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Pdf.CurrentPage.TextOut(50, 740, 0, 'Quarterly figures, draft for review');

  Pdf.CurrentPage.AddTextAnnotation(
    'Confirm the totals before sign-off.',
    Rect(50, 720, 70, 740), False, taComment, clBlue);
  Pdf.CurrentPage.AddFreeTextAnnotation(
    'DRAFT', Rect(450, 720, 540, 745), ftCenter, clRed);
  Pdf.CurrentPage.AddStampAnnotation(
    'For comment', Rect(50, 660, 180, 695), satForComment, clGreen);

  Pdf.EndDoc;
finally
  Pdf.Free;
end;

Un último reflejo que vale la pena desarrollar cuando la salida no parece correcta: abrid el fichero en más de un visor antes de decidir que el código está roto. Los sellos y los iconos de nota menos comunes suelen ser los culpables, y como la anotación es una solicitud al lector más que píxeles pintados, una diferencia entre Acrobat y un visor ligero es a menudo la especificación funcionando según el diseño, no un error en vuestra llamada.

Las llamadas de anotación mostradas aquí forman parte del HotPDF Component para Delphi y C++Builder.