HotPDF riempie i campi comb AcroForm con testo arabo, ebraico, segni combinanti e a direzione mista tramite THPDFHeadlessDocument.SetTextFieldValueWithFamilies. La griglia comb si conta in scalari Unicode, una legatura o una lettera base con i suoi segni viene centrata come un solo cluster sulle k celle che i suoi scalari possiedono, e l'intera riga passa per l'algoritmo bidirezionale Unicode prima che venga assegnata una qualunque cella
Ognuna di queste tre regole rimpiazza una scorciatoia che in una demo latina sembra a posto e si sfalda alla prima form vera. Immaginate una domanda governativa con un campo comb da dieci caselle per un nome scritto in arabo. Riempietelo con logica per carattere e avete lettere in forme isolate marciare da sinistra a destra, una legatura lam-alef schiacciata in una casella mentre la vicina sta vuota, e una fatha fluttuante sulla casella sbagliata. Ogni uno di quei difetti viene dal contare l'unità sbagliata o dall'ordinare il testo al momento sbagliato
Cosa chiede davvero la flag Comb a un writer PDF?
La flag Comb chiede allo writer di dividere il campo in esattamente MaxLen posizioni equidistanti e di mettere un carattere in ciascuna. ISO 32000-1 §12.7.4.3 definisce Comb come bit posizione 25 nella Table 228, le field flag specifiche dei campi testo, e lo rende significativo solo quando la voce MaxLen della Table 229 è presente e le flag Multiline, Password e FileSelect sono a zero. Il bit in sé è $1000000 nel valore /Ff a 32 bit; se avete mai visto campi comb comportarsi male dopo un viaggio di andata e ritorno in una variabile in virgola mobile, l'articolo sulle field flag AcroForm a 32 bit spiega perché quel bit è il primo a andarsene
La specifica dice "carattere" e si ferma lì. Fu scritta pensando a caselle latine a passo fisso e non dice cosa sia un carattere quando una coppia di lettere arabe diventa un solo glyph, o quando un segno vocalico non ha larghezza propria. HotPDF deve scegliere un'unità, e mentre lo fa impone le regole strutturali in modo severo: un campo comb che sia anche multiline, o che non porti un MaxLen positivo, viene rifiutato con una diagnostica, e i campi password non possono essere campi comb per niente
Perché contare gli scalari Unicode invece che unità UTF-16, glyph o grapheme?
HotPDF conta le celle comb in scalari Unicode perché è la stessa unità che usa per imporre MaxLen, e la griglia e il controllo di lunghezza devono concordare o un valore che supera la validazione non entrerà nelle sue caselle. Le tre alternative allettanti rompono ciascuna quell'accordo in modo diverso
- le code unit UTF-16 sono ciò che restituisce la
Lengthdi Delphi, quindi un carattere supplementare come U+20BB7 mangerebbe due caselle e due posti del budget del campo - i conteggi di glyph dipendono dal font: la maggior parte dei font arabi trasforma lam più alef in un solo glyph, quindi il numero di celle cambierebbe nel momento in cui cambiate carattere
- i conteggi di grapheme fondono una lettera base con i suoi segni, il che significa che la griglia sarebbe in disaccordo con un controllo
MaxLenche conta ogni scalare digitato dall'utente
La validazione MaxLen in THPDFHeadlessDocument percorre la stringa solo quando la sua lunghezza UTF-16 supera il limite, e quando lo fa, conta una coppia di surrogate valida una volta sola e rifiuta in toto una surrogate non appaiata. Se la vostra UI vuole avvisare prima della chiamata invece di beccare un'eccezione dopo, contate allo stesso modo
// Conta gli scalari Unicode come fa la griglia comb:
// una coppia di surrogate valida è uno scalare
function CountScalars(const S: UnicodeString): Integer;
var
I: Integer;
begin
Result := 0;
I := 1;
while I <= Length(S) do
begin
if (Ord(S[I]) >= $D800) and (Ord(S[I]) <= $DBFF) and
(I < Length(S)) and
(Ord(S[I + 1]) >= $DC00) and (Ord(S[I + 1]) <= $DFFF) then
Inc(I, 2)
else
Inc(I);
Inc(Result);
end;
end;
Alcuni valori mostrano quanto lontano le unità divergono, e perché solo la colonna degli scalari corrisponde a ciò per cui un campo comb fa il budget
| Valore | Unità UTF-16 | Scalari (celle) | Glyph tipici |
|---|---|---|---|
| Lam + alef (لا) | 2 | 2 | 1 legatura |
| Beh + fatha (بَ) | 2 | 2 | 2, segno attaccato alla base |
| U+20BB7 (𠮷) | 2 | 1 | 1 |
| Decomposed e + U+0301 | 2 | 2 | 1 o 2, dipende dal font |
Come legature e segni combinanti condividono le loro celle?
Un cluster shaped che copre k scalari occupa k celle contigue e viene centrato come unità unica su di esse. Dopo lo shaping, ogni cluster viene rimappato all'esatta porzione di testo sorgente da cui viene, così HotPDF sa quali scalari, e quindi quali celle, possiede. La legatura lam-alef di sopra prende due caselle e sta centrata su entrambe. Un beh che porta uno shadda e una fatha è tre scalari, quindi prende tre caselle, con i segni ancora attaccati alla lettera
Il centraggio è dove l'implementazione ovvia va storta. Centrare ogni glyph nella sua casella strappa un segno dalla sua base, perché la posizione del segno ha senso solo relativamente alla lettera che decora. HotPDF tiene ogni glyph dentro un cluster al suo advance shaped e ai suoi offset X e Y, poi centra il cluster usando l'estensione orizzontale combinata dell'advance naturale, delle origini dei glyph posizionati e dell'inchiostro effettivo dei glyph. Il cluster si muove come blocco; niente dentro viene riarrangiato
Una conseguenza è facile da perdere. I controlli direzionali come U+200F RIGHT-TO-LEFT MARK, U+061C ARABIC LETTER MARK, e i controlli di embedding e isolate sono scalari Unicode senza glyph. Contano contro MaxLen e tengono una cella che resta vuota. Se i vostri dati sorgente vengono da un sistema che cosparge questi segni nei nomi, ripuliteli prima del riempimento, o il campo mostrerà una lacuna e rifiuterà valori un carattere prima di quanto l'utente si aspetti
Perché il riordino bidi deve avvenire prima che le celle vengano assegnate?
HotPDF fa passare la riga completa per l'algoritmo bidirezionale Unicode (UAX #9) prima, e solo dopo dà a ogni scalare la sua cella dalla sua posizione visiva. Assegnare le celle in ordine logico e ribaltare il risultato dopo funziona per una stringa puramente araba e fallisce non appena la riga contiene cifre o un token latino, perché quelle sequenze devono continuare a leggersi da sinistra a destra dentro la riga da destra a sinistra. Un valore come un codice di riferimento con prefisso arabo e numero tiene le sue cifre nel giusto ordine solo se il riordino vede l'intera riga in una volta. La panoramica sullo shaping dei testi complex script copre il problema dell'ordine logico contro visivo più a fondo
L'allineamento poi posiziona l'intera griglia di celle usate dentro il campo. La voce /Q ereditata dal campo vince quando presente; Options.Alignment si applica solo quando la form non ne ha. Questo conta per le form da destra a sinistra: un breve valore arabo in un campo comb allineato a sinistra riempie le caselle più a sinistra, il che è corretto secondo la specifica e quasi mai ciò che il progettista della form intendeva. Sistemate il /Q della form, o impostate htaRight quando il campo non ha un quadding proprio. La rotazione del widget viene dalla voce /R del /MK, e a 90 o 270 gradi il viewport logico del testo scambia larghezza e altezza mentre la logica delle celle resta uguale
Riempire da Delphi un campo comb a font misti
Il testo comb a font misti usa una sola griglia di celle, una sola dimensione font e una sola baseline per ogni font fornito. Con FallbackFontFamilies a True, HotPDF sceglie un font per ogni grapheme completo dalle famiglie fornite nel loro ordine configurato, così un carattere arabo può portare le lettere mentre uno latino porta l'ASCII incorporato. Ogni porzione di font viene shaped con l'intera riga come contesto, un segno non viene mai separato dalla sua base tra font diversi, e nessuna legatura viene inventata oltre un confine di font. Impostare ogni porzione con la sua griglia locale o ridimensionarla al suo bounding box sarebbe più semplice, e romperebbe sia le posizioni delle celle sia i campi ruotati nel momento in cui il fallback cambiasse font a metà riga
uses
System.SysUtils, System.IOUtils,
HPDFHeadlessDocument, HPDFHeadlessAppearance;
procedure FillArabicComb(const SourceFile, TargetFile, Value: string);
var
Doc: THPDFHeadlessDocument;
Field: THPDFHeadlessFormField;
Families: THPDFHeadlessRichTextFontFamilies;
Options: THPDFHeadlessTextAppearanceOptions;
FieldIndex: Integer;
begin
Doc := THPDFHeadlessDocument.Create;
try
Doc.LoadFromFile(SourceFile);
FieldIndex := Doc.FindFormField('applicant.name_ar');
if FieldIndex < 0 then
raise Exception.Create('Field not found');
Field := Doc.GetFormField(FieldIndex);
if (Field.Flags and $1000000) = 0 then // bit 25: Comb
raise Exception.Create('Not a comb field');
SetLength(Families, 2); // da una a sedici famiglie
Families[0].FamilyName := 'Noto Naskh Arabic';
Families[0].Fonts[hrfsRegular] :=
TFile.ReadAllBytes('NotoNaskhArabic-Regular.ttf');
Families[1].FamilyName := 'Noto Sans';
Families[1].Fonts[hrfsRegular] :=
TFile.ReadAllBytes('NotoSans-Regular.ttf');
Options := THPDFHeadlessTextAppearanceOptions.Default;
Options.ShapeComplexText := True; // già il default
Options.FallbackFontFamilies := True; // il default è False
Options.FontSize := 0; // auto-fit, tetto a 12
Options.Alignment := htaRight; // usato solo quando /Q manca
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
Doc.SaveToFile(TargetFile); // deve differire da SourceFile
finally
Doc.Free;
end;
end;
Notate cosa l'esempio non imposta. Durante il riempimento del documento, Width, Height, Rotation e CombCells derivano dal rettangolo di ogni widget, dalla sua voce /MK e dal MaxLen del campo, quindi i valori che mettete in quei membri vengono sovrascritti. Uno FontSize zero chiede il dimensionamento automatico, che confronta l'estensione di ogni cluster con la larghezza delle sue k celle e con l'altezza condivisa di metrica e inchiostro di tutti i font partecipanti; una dimensione fissa viene semplicemente ritagliata al widget. CharacterSpacing e WordSpacing non spostano mai le posizioni comb, dato che la griglia da sola decide dove atterra un cluster. HotPDF carica i suoi runtime HarfBuzz e FriBidi solo quando un valore richiede davvero lo shaping, e i valori che non lo richiedono mantengono il layout comb diretto esistente invariato. Per il flusso generale di riempimento di un PDF caricato, incluse permessi e salvataggio, vedete impostare i valori dei campi form in un PDF caricato
Cosa non garantisce un'appearance comb shaped?
Un'appearance comb shaped garantisce assegnazione corretta delle celle, cluster integri e un valore logico preservato; non garantisce giunzioni corsive senza cuciture né un'estrazione del testo perfetta in ogni strumento. Entrambi i limiti discendono da ciò che un campo comb è, e conviene conoscerli prima di promettere niente a un cliente
Le giunzioni corsive tra celle sono il primo. Le lettere arabe vengono shaped comunque con il contesto dell'intera riga, quindi ognuna prende la sua forma iniziale, mediale o finale corretta, ma il passo fisso delle celle mette spazio tra cluster separati e i tratti di collegamento non si incontreranno come nel testo scorrevole. È insito nel disegnare una scrittura di collegamento in caselle, e nessun centraggio lo nasconde
L'estrazione del testo è il secondo. Il /V del campo conserva il valore logico semplice, e il Form XObject padre dell'appearance porta uno span /ActualText (ISO 32000-1 §14.9.4) con l'input completo; i Form figli non ne portano. Gli estrattori che onorano ActualText recuperano la stringa originale. Gli estrattori che la ignorano ricostruiscono il testo dalle posizioni dei glyph e possono restituire un ordine diverso o duplicare il testo dei cluster, perché un cluster multi-glyph non ha un singolo valore Unicode ovvio per glyph. Se un sistema a valle vuole il valore, leggete il campo, non il testo della pagina
I confini rimanenti sono meccanici. Il testo comb deve essere una sola riga senza tab, e un valore con più scalari che celle fallisce invece di essere troncato. I fallimenti sono transazionali: un glyph mancante, un font non valido, uno sforamento di budget o una cancellazione lasciano il valore e le appearance precedenti intatti, così un riempimento rifiutato non lascia mai dietro un campo mezzo disegnato
try
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
except
on E: EHPDFHeadlessDocumentError do
if E.Code = hdLimitExceeded then
ShowFieldError('The value has more characters than the field allows')
else
raise;
on E: EHPDFHeadlessAppearanceError do
if E.Failure = haMissingGlyph then
ShowFieldError('None of the supplied fonts covers this text')
else
raise;
end;
// Su entrambi gli errori il campo conserva il suo valore e appearance precedenti
Dove stanno i campi comb shaped in una pipeline di form
Il supporto comb shaped conta di più per le form che esistono già: dichiarazioni fiscali, domande di visto, pacchetti di onboarding bancario e registrazioni di documenti d'identità progettate con campi a caselle e ora riempite in arabo, ebraico, persiano o urdu. Contate in scalari, conservate il valore originale in /V, lasciate che una sola griglia governi ogni font, e testate con un valore che mescoli lettere da destra a sinistra, cifre e un segno combinante, dato che quella singola stringa esercita ogni regola di questo articolo. L'API headless dei documenti, lo shaping comb e il fallback dei font descritti qui fanno parte dell'HotPDF Delphi PDF component