Articolo tecnico

Annotazioni PDF in Delphi con HotPDF: tipi e rettangoli

Un'annotazione non è contenuto di pagina. Quando chiamate TextOut o disegnate un rettangolo, i segni diventano parte del content stream della pagina, incisi nei byte che un renderer dipinge. Un'annotazione è un dizionario separato che pende dalla pagina attraverso il suo array /Annots, con il proprio rettangolo, il proprio aspetto e il proprio ciclo di vita. Un lettore può aprirla, spostarla, nasconderla o rimuoverla senza toccare un singolo glifo della pagina sottostante. Quella separazione è l'intera ragione per cui le annotazioni esistono, ed è anche la fonte delle due cose che sorprendono per prime: dove atterra un'annotazione e che aspetto ha una volta che un particolare visualizzatore la prende in carico

HotPDF espone i sottotipi di annotazione di ISO 32000 attraverso una famiglia di chiamate AddXxxAnnotation sull'oggetto pagina. Condividono tutte la stessa forma: un rettangolo che fissa l'annotazione sulla pagina nello spazio utente PDF, un carico utile (testo, il nome di un timbro, una coppia di punti) e un colore. Azzeccate il rettangolo e la maggior parte del lavoro è fatta. Il resto è sapere quali sottotipi portano con sé il proprio aspetto e quali si appoggiano al visualizzatore per essere disegnati

Una pagina PDF prodotta da HotPDF che mostra icone di note di testo, riquadri di testo libero, markup a quadrato e a linea e timbri di approvazione distribuiti sulla pagina
Una pagina che porta più sottotipi di annotazione insieme: note di testo, testo libero, markup geometrici e timbri

Il rettangolo è l'annotazione, non il testo

Ogni chiamata di annotazione accetta un TRect, e quel rettangolo significa qualcosa di diverso dalle coordinate che passate a TextOut. Per una nota di testo è la zona cliccabile, la piccola regione dove sta l'icona della nota e dove un clic fa comparire il commento. Per un quadrato o un riquadro di testo libero è l'estensione visibile del markup. Per un timbro è il riquadro dentro cui l'artwork del timbro viene scalato. I numeri sono punti dello spazio utente PDF, misurati dall'angolo inferiore sinistro della pagina con Y crescente verso l'alto, la stessa convenzione che usa il resto di HotPDF

Una nota di testo è il sottotipo più leggero. Le date il testo del corpo, un rettangolo per l'icona, un flag che indica se si apre di default, un nome di icona e un colore

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

Il rettangolo qui è deliberatamente piccolo, circa venti punti per lato, perché una nota di testo è solo un'icona finché qualcuno non la clicca. Ingrandite il rettangolo e non otterrete una nota grande; otterrete una zona di clic sovradimensionata con l'icona inchiodata a un angolo. Il flag Open controlla se il popup è visibile al caricamento del documento. Impostate una manciata di note a True e si impileranno una sull'altra e sopra il contenuto, quindi riservatelo all'unica nota che volete davvero che il lettore veda immediatamente

Il nome dell'icona viene da THPDFTextAnnotationType, che corrisponde alle icone di nota standard: taComment, taKey, taNote, taHelp, taParagraph, taNewParagraph e taInsert. L'icona è l'unica cosa che il tipo cambia. Non altera il comportamento, e vale la pena sapere che non tutti i visualizzatori disegnano tutte e sette; quelle sicure tra lettori vecchi e nuovi sono taComment, taNote e taHelp

Il testo libero scrive sulla pagina, ma resta un'annotazione

Un'annotazione di testo libero sembra contenuto perché il testo è visibile senza clic, adagiato nel suo rettangolo come una didascalia. È comunque un'annotazione, con tutta la separabilità che ciò implica, che è esattamente quello che volete per un timbro di revisione o un'etichetta di bozza che qualcuno dovrebbe poter rimuovere in seguito. La firma sostituisce l'icona e il flag di apertura con un valore di giustificazione

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

Qui il rettangolo conta più che per una nota di testo, perché il testo va a capo e si allinea al suo interno. Dimensionate il riquadro troppo basso e il testo si tronca sul bordo inferiore; troppo stretto e va a capo in punti che non intendevate. La giustificazione viene da THPDFFreeTextAnnotationJust e ha solo i tre valori. Poiché il testo libero è un'annotazione di markup, un lettore che apre il file in un editor può selezionarla, spostarla o eliminarla come unità, ed è questa la differenza che decide se ricorrere al testo libero o disegnare semplicemente le parole con TextOut. Se l'etichetta deve essere permanente, disegnatela. Se è editoriale e destinata a essere rimossa, fatene un'annotazione

Markup geometrici e a linea per indicare le cose

Quadrati, cerchi e linee sono il markup che usate per indicare una regione anziché descriverla a parole. AddCircleSquareAnnotation copre le due forme di riquadro attraverso un THPDFCSAnnotationType di csCircle o csSquare, con il rettangolo che dà i limiti della 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;

Notate che l'annotazione a linea rompe lo schema del rettangolo: accetta due record THPDFCurrPoint, un inizio e una fine, perché una linea è definita dai suoi estremi, non da un riquadro di delimitazione. Il colore imposta il tratto. Se volete punte di freccia, HotPDF ha overload di AddLineAnnotation che accettano stili di terminazione linea, ma la forma semplice a tre argomenti disegna una linea nuda, che di solito è ciò che un richiamo desidera

I sottotipi di markup testuale lavorano su una regione che avete già impaginato. AddHighlightAnnotation accetta un rettangolo, contenuti opzionali e un colore che di default è giallo, e tinge l'area come farebbe un evidenziatore. È pensata per stare sopra testo reale, quindi il rettangolo dovrebbe combaciare con i limiti delle parole che avete disegnato, il che significa che in genere lo calcolate dalle stesse coordinate che avete passato a TextOut anziché indovinarlo

I timbri dipendono dal visualizzatore per il rendering

Un'annotazione timbro è quella con più probabilità di apparire diversa da un lettore all'altro, e il motivo merita di essere capito. AddStampAnnotation nomina un timbro standard attraverso THPDFStampAnnotationType, con valori come satApproved, satConfidential, satFinal, satDraft e satForComment

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

Il nome del timbro è una richiesta. Il PDF definisce l'insieme dei nomi di timbro standard ma non l'artwork che vi sta dietro, quindi ogni visualizzatore fornisce la propria resa di "APPROVED" o "CONFIDENTIAL", e alcuni non rendono nulla per i nomi che non riconoscono. Il rettangolo controlla il riquadro in cui l'artwork viene scalato, e il colore è un suggerimento che il visualizzatore può onorare o meno. Se un timbro deve avere lo stesso aspetto ovunque, la strada affidabile non è affatto un timbro standard: disegnate il segno voi stessi con TextOut e le chiamate di disegno, oppure inseritelo come annotazione di testo libero il cui aspetto controllate. Ricorrete al timbro standard quando volete il look familiare del visualizzatore e potete tollerare la variazione

Gli allegati di file seguono la stessa forma rettangolo-più-carico-utile. AddFileAttachmentAnnotation accetta la descrizione, il percorso del file da incorporare, un rettangolo per l'icona a graffetta e un colore. Il file viaggia dentro il PDF, e l'icona è la maniglia che un lettore usa per estrarlo

In cosa le annotazioni differiscono dai campi AcroForm

La confusione che costa più tempo è trattare un'annotazione come se fosse un campo modulo. Entrambi si agganciano alla pagina attraverso /Annots, e un campo modulo è di fatto un sottotipo speciale di annotazione (un widget), motivo per cui sembrano imparentati. Non sono intercambiabili. Un campo modulo contiene un valore, ha un nome, partecipa all'ordine di tabulazione e può essere inviato, azzerato o gestito da script; quelli si creano con le chiamate AddTextField, AddCheckBox e AddPushButton, non con le chiamate di annotazione di questa pagina. Un'annotazione di markup contiene un commento o una forma, non ha alcun valore da inviare, ed è lo strumento sbagliato nel momento in cui dovete raccogliere input

Il test pratico è semplice. Se un utente deve digitare, scegliere o cliccare e il documento deve ricordarselo, vi serve un campo AcroForm. Se state lasciando una nota, marcando una regione o timbrando uno stato che viaggia con il file ma non è un dato, vi serve un'annotazione. Confonderli produce documenti che sembrano giusti e si comportano male: un "campo" che nessuno può compilare, o un commento che svanisce quando un modulo viene azzerato. Il lato interattivo, con tipi di campo, validazione e azioni di invio, è un argomento a sé trattato nella guida ai campi e alle azioni AcroForm

Comporre una pagina

I pezzi si compongono come il resto di HotPDF. Impostate le proprietà del documento, chiamate BeginDoc, disegnate il contenuto di pagina che vi serve con le chiamate di testo e grafica, aggiungete le annotazioni sopra e chiudete con EndDoc. Le annotazioni si agganciano a CurrentPage, quindi dopo un AddPage atterrano sulla nuova pagina, e una nota che intendevate per la pagina uno apparirà zitta zitta sulla pagina due se la aggiungete dopo l'interruzione

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 ultimo riflesso da costruire quando l'output sembra sbagliato: aprite il file in più di un visualizzatore prima di decidere che il codice è rotto. Timbri e icone di nota più rare sono i soliti sospetti, e poiché l'annotazione è una richiesta al lettore anziché pixel dipinti, una differenza tra Acrobat e un visualizzatore leggero è spesso la specifica che funziona come previsto, non un bug nella vostra chiamata

Le chiamate di annotazione mostrate qui fanno parte del HotPDF Component per Delphi e C++Builder