Articolo tecnico

HotPDF TextOut in Delphi: dimensione, stile, rotazione e spaziatura

Ogni stringa visibile in un documento HotPDF arriva tramite una sola chiamata: TextOut(X, Y, angle, Text). L'esempio Hello World la usa nella sua forma più essenziale, con il font impostato una volta e quattro argomenti lasciati ai valori predefiniti sensibili. Oltre quella prima pagina, gli stessi quattro argomenti portano tutto il peso del layout. Il terzo ruota la riga. Il font impostato appena prima ne decide dimensione e stile. E la coppia X, Y, misurata dall'angolo della pagina in punti, è l'unica cosa che separa un report pulito da testo sovrapposto, ritagliato o scivolato di una riga su una stampante altrui. È qui che TextOut dà il meglio di sé, e dove i valori predefiniti smettono di bastare.

Vale la pena fissare la firma nella memoria prima di tutto il resto: X e Y sono Single in punti, angle è un Extended in gradi, e Text è una WideString, quindi l'Unicode passa senza una chiamata separata. Un secondo overload accetta un PWORD più una lunghezza per quando si tengono già in mano codici di glifo, ma per le stringhe ordinarie si usa la forma WideString.

Dimensione e stile vengono da SetFont, non da TextOut

TextOut non ha un parametro per la dimensione. La dimensione, il peso, l'inclinazione, tutto risiede nella chiamata SetFont che precede la riga, e rimane attivo finché il SetFont successivo non lo sostituisce. Questo è il fatto singolo che spiega la maggior parte della confusione del primo giorno: una riga esce in grassetto perché tre chiamate prima qualcosa ha impostato [fsBold] e nulla lo ha azzerato.

Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report');        // 24pt regular

Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue');                 // 12pt bold

Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands');    // 11pt italic

Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, '  +18.4% YoY');            // styles combine

Il secondo argomento è un set TFontStyles, quindi [fsBold, fsItalic] è grassetto corsivo e [] è normale. La dimensione è in punti, la stessa unità delle coordinate, il che rende facile ragionare sulla spaziatura verticale: una riga da 12 punti ha bisogno di circa 14-16 punti di passo verticale per respirare, quindi diminuire Y di 14 per riga è un'interlinea di partenza ragionevole. Non esiste un avanzamento automatico di riga. Calcoli ogni baseline tu stesso, il che è tedioso per un paragrafo ma preciso per un modulo, dove ogni campo si trova a una coordinata fissa.

Due note pratiche sul nome del font. Viene risolto rispetto ai font installati sulla macchina di build, e qualunque cosa il sistema operativo restituisca è ciò che viene incorporato, quindi un nome che si risolve sul tuo desktop e uno che si risolve su un server di build non sono garantiti essere lo stesso typeface. E il font deve coprire le scritture presenti nella stringa. Una sequenza di testo cirillico o CJK sotto un typeface solo-latino viene renderizzato come caselle di glifo mancante senza alcun errore, motivo per cui la pagina Hello World usa un typeface Unicode ampio quando mescola lingue.

Pagina HotPDF TextOut che mostra Arial, Times New Roman e Courier New renderizzati con stili normale, grassetto e corsivo su vari set di caratteri

L'argomento angle ruota attorno all'ancora

Il terzo argomento è quello che la maggior parte del codice lascia a zero per sempre. Passa un valore diverso da zero e la riga ruota in senso antiorario attorno alla propria ancora (X, Y), l'angolo in basso a sinistra del testo, di quel numero di gradi. L'ancora stessa non si sposta, quindi la stessa coordinata che posiziona un'etichetta orizzontale posiziona la sua gemella ruotata; cambia solo la direzione in cui i glifi avanzano.

Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

Novanta gradi è il caso più comune, un'etichetta che scorre lungo il lato di un grafico o un titolo sul dorso. Quarantacinque gradi gestisce le intestazioni di colonna inclinate, il trucco che permette a un'etichetta larga di stare sopra una colonna stretta senza invadere quelle adiacenti. La rotazione non cambia come viene interpretata l'ancora, il che confonde le persone: una riga a 90 gradi inizia ancora da (X, Y) e cresce verso l'alto da lì, quindi per centrare un'etichetta ruotata si regola l'ancora, non l'angolo. Quando più righe ruotate condividono una baseline, date loro lo stesso Y e scalate X, esattamente come si scala Y per righe orizzontali sovrapposte.

Posizionare le coordinate senza indovinare

Le coordinate sono la parte che sopravvive alla revisione o che la manda in silenzio. HotPDF misura dall'angolo in basso a sinistra della pagina, con Y che cresce verso l'alto, in punti a 72 per pollice. Una pagina US Letter è 612 per 792 punti; A4 è 595 per 842. Un margine superiore di un pollice su Letter posiziona quindi la prima baseline vicino a Y = 792 meno 72 meno la dimensione del font, non a un piccolo numero vicino all'alto. Chi arriva dalle coordinate schermo, dove Y cresce verso il basso da zero, scrive la prima riga fuori dal bordo inferiore e passa dieci minuti a chiedersi dove sia finita.

Tratta il layout come aritmetica rispetto ad ancore denominate piuttosto che una colonna di numeri magici. Un margine sinistro, una baseline progressiva che decrementi per riga e un'interlinea fissa trasformano un blocco di etichette in un breve ciclo invece di un muro di letterali:

const
  LeftMargin = 72;        // 1 inch in
  TopBaseline = 720;       // first line, ~1 inch down on Letter
  Leading = 16;            // vertical step between lines
var
  Y: Single;
  Line: string;
begin
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Y := TopBaseline;
  for Line in ReportLines do
  begin
    Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
    Y := Y - Leading;
    if Y < 72 then            // bottom margin reached
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // font resets on a new page
      Y := TopBaseline;
    end;
  end;
end;

Il controllo dell'interruzione di pagina è la riga che tutti dimenticano per prima e che il campo colpisce più duramente. Non c'è un layout a flusso sotto TextOut. Decrementa oltre il margine inferiore e il testo continua a disegnarsi nella grondaia, fuori dalla pagina, nel nulla, senza avvisi. Quindi tieni d'occhio Y, chiama AddPage quando supera il limite inferiore, e reimposta la baseline. Il SetFont dopo AddPage non è imbottitura opzionale: il font corrente non sopravvive a un'interruzione di pagina, e la prima riga sulla nuova pagina viene renderizzata nel typeface predefinito del visualizzatore se lo ometti.

Spaziatura tra caratteri e parole per adattamento e allineamento

A volte una stringa è corretta ma ha la larghezza sbagliata: un'intestazione che deve attraversare una riga fissa, un codice che dovrebbe essere letto con cifre più ariose, una colonna che ha bisogno di valori leggermente spostati per allinearsi. PDF porta due operatori di stato del testo per questo, la spaziatura tra caratteri (Tc, spazio extra aggiunto dopo ogni glifo) e la spaziatura tra parole (Tw, spazio extra aggiunto a ogni carattere spazio), ed entrambi sono espressi in unità di spazio testo non scalate, effettivamente punti alla dimensione del font corrente. Sono stato, non argomenti di TextOut, quindi si impostano, si disegna, e si reimpostano.

// Letter-space a short heading so it stretches across a rule.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0);   // reset before normal body text

// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

La spaziatura tra parole agisce solo sul carattere spazio (codice 32), il che ha una conseguenza da conoscere: non fa nulla all'interno di una sequenza CJK che non ha spazi ASCII, e interagisce in modo strano con il testo codificato come indici di glifo anziché come byte. Per l'output tabulare latino è il modo economico di ampliare i gap senza riscrivere la stringa. La spaziatura tra caratteri è lo strumento migliore per un'intestazione che deve raggiungere una larghezza target, poiché distribuisce la regolazione uniformemente su ogni glifo invece di raccoglierla agli spazi.

Il reset è tutta la disciplina. La spaziatura, come il font, fa parte dello stato di disegno della pagina, e lo stato persiste finché non lo si cambia. Spaziatura un'intestazione e dimentica di azzerarla, e ogni paragrafo sotto eredita lo stretching, che si legge come una sottile, difficile-da-collocare stortura che sopravvive a una revisione superficiale e fallisce a una attenta. L'abitudine affidabile è impostare un valore di spaziatura, disegnare la riga che ne ha bisogno, e riportarlo a zero nella riga successiva, in modo che nessun codice successivo debba sapere cosa ha fatto una sezione precedente.

Pagina HotPDF TextOut che confronta la scalatura orizzontale del testo, la spaziatura tra caratteri, la spaziatura tra parole e le modalità di rendering fill e stroke

Verificare l'output dove effettivamente si rompe

Il layout del testo fallisce sulla seconda macchina, non sulla prima, quindi i controlli che contano avvengono lontano dalla tua scrivania. Apri il file generato su un sistema senza il tuo set di font per sviluppatori installato e conferma che i typeface incorporati vengano ancora renderizzati, incluse le lettere latine accentate, qualsiasi scrittura non-latina e la punteggiatura, in un solo passaggio anziché controllare a campione i caratteri più facili. Seleziona e copia alcune righe per confermare che il testo sia testo reale e non contorni, il che conta nel momento in cui la ricerca o l'estrazione entrano in gioco. Alimenta il layout con dati rappresentativi, l'etichetta tedesca più lunga e il numero più largo, non un comodo segnaposto, perché la riga che invade un campo è sempre quella che non hai digitato a mano. E se la pagina deve atterrare su un modulo prestampato, stampa o rasterizza un campione e sovrapponilo all'originale; una deriva della baseline di un quarto di millimetro è invisibile sullo schermo e ovvia sulla carta.

Se non hai ancora scritto una singola pagina, inizia con l'esempio Hello World di HotPDF, che configura il documento, il font e il sistema di coordinate con angolo in basso a sinistra da cui tutto quanto sopra dipende. Le chiamate TextOut, SetFont e di spaziatura mostrate qui fanno parte del HotPDF Component per Delphi e C++Builder.