HotPDF udfylder AcroForm comb-felter med arabisk, hebraisk, combining marks og tekster med blandet retning gennem THPDFHeadlessDocument.SetTextFieldValueWithFamilies. Comb-gitteret tælles i Unicode-scalarer, en ligatur eller et basustegn med sine mærker centreres som ét cluster hen over de k celler, dens scalarer ejer, og hele linjen køres gennem Unicode Bidirectional Algorithm, før nogen celle tildeles
Hver af de tre regler erstatter en genvej, der ser fin ud i en Latin-demo og falder fra hinanden på den første rigtige formular. Forestil dig en offentlig ansøgning med et comb-felt på ti bokse til et navn skrevet på arabisk. Udfylder du det med per-tegn-logik, får du bogstaver i isolerede former, der marcherer fra venstre mod højre, en lam-alef-ligatur presset ned i én boks, mens naboboksen står tom, og en fatha, der svæver over den forkerte celle. Hver af de defekter stammer fra at tælle den forkerte enhed eller ordne teksten på det forkerte tidspunkt
Hvad beder Comb-flaget faktisk en PDF-writer om at gøre?
Comb-flaget beder writeren om at opdele feltet i præcis MaxLen ligeligt fordelte positioner og sætte ét tegn i hver. ISO 32000-1 §12.7.4.3 definerer Comb som bitposition 25 i Table 228, de feltflag, der gælder tekstfelter, og gør den kun meningsfuld, når MaxLen-posten fra Table 229 er til stede, og Multiline-, Password- og FileSelect-flagene er ryddet. Bitten selv er $1000000 i 32-bit /Ff-værdien; har du nogensinde set comb-felter opføre sig mærkeligt efter en tur gennem en floating-point-variabel, forklarer artiklen om 32-bit AcroForm feltflag, hvorfor netop den bit forsvinder først
Specifikationen siger "character" og stopper der. Den er skrevet med fastbredde-latinske bokse i tankerne og siger ikke, hvad et tegn er, når ét arabisk bogstavpar bliver til ét glyph, eller når et vokalmærke ikke har nogen egen bredde. HotPDF må vælge en enhed, og den håndhæver de strukturelle regler strengt undervejs: et comb-felt, der også er multiline, eller som ikke har en positiv MaxLen, afvises med en diagnostik, og passwordfelter kan slet ikke være comb-felter
Hvorfor tælle Unicode-scalarer i stedet for UTF-16-enheder, glyphs eller grafemer?
HotPDF tæller comb-celler i Unicode-scalarer, for det er samme enhed, den bruger til at håndhæve MaxLen, og gitteret og længdetjekket skal være enige, ellers passer en værdi, der består valideringen, ikke i sine bokse. De tre fristende alternativer bryder hver på sin måde den overensstemmelse
- UTF-16-kodeenheder er det, Delphis
Lengthreturnerer, så et supplementary-tegn som U+20BB7 ville spise to bokse og to pladser af feltbudgettet - Glyph-antal afhænger af fonten: de fleste arabiske fonte gør lam plus alef til ét glyph, så celletallet ville ændre sig, i samme øjeblik du skifter typeface
- Grafem-antal smelter et basustegn sammen med sine mærker, hvilket betyder, at gitteret ville stride med et
MaxLen-tjek, der tæller hver scalar, brugeren tastede
MaxLen-valideringen i THPDFHeadlessDocument gennemløber kun strengen, når dens UTF-16-længde overstiger grænsen, og når den gør, tæller den et gyldigt surrogate-par én gang og afviser et uparret surrogate på stedet. Vil din UI advare før kaldet i stedet for at fange en exception efter det, så tæl på samme måde
// Tæller Unicode-scalarer som comb-gitteret gør:
// et gyldigt surrogate-par er én scalar
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;
Et par værdier viser, hvor langt enhederne driver fra hinanden, og hvorfor kun scalar-kolonnen matcher, hvad et comb-felt budgetterer med
| Værdi | UTF-16-enheder | Scalarer (celler) | Typiske glyphs |
|---|---|---|---|
| Lam + alef (لا) | 2 | 2 | 1 ligatur |
| Beh + fatha (بَ) | 2 | 2 | 2, mærke fastgjort til basen |
| U+20BB7 (𠮷) | 2 | 1 | 1 |
| Decomposed e + U+0301 | 2 | 2 | 1 eller 2, afhænger af fonten |
Hvordan deler ligaturer og combining marks deres celler?
Et shappet cluster, der dækker k scalarer, optager k sammenhængende celler og centreres som én enkelt enhed hen over dem. Efter shaping mapkes hvert cluster tilbage til det præcise span af kildetekst, det kom fra, så HotPDF ved, hvilke scalarer — og dermed hvilke celler — det ejer. Lam-alef-ligaturen ovenfor får to bokse og sidder centreret hen over begge. En beh med en shadda og en fatha er tre scalarer, så den får tre bokse, med mærkerne stadig fastgjort til bogstavet
Centrering er der, hvor den åbenlyse implementation går galt. Centerer du hvert glyph i sin egen boks, trækker du et mærke væk fra sin base, for mærkets position betyder kun noget relativt til bogstavet, det pynter. HotPDF beholder hvert glyph i et cluster ved dets shappede advance og dets X- og Y-offsets og centrerer derefter clusteret ud fra den samlede vandrette udstrækning af natural advance, de positionerede glyph-origins og den faktiske glyph-ink. Clusteret flytter sig som en blok; intet indeni omrokeres
En konsekvens er let at overse. Retningskontroller som U+200F RIGHT-TO-LEFT MARK, U+061C ARABIC LETTER MARK og embedding- og isolate-kontrollerne er Unicode-scalarer uden glyph. De tæller med i MaxLen og holder en celle, der forbliver tom. Kommer dine kildedata fra et system, der drysser disse mærker ud i navne, så fjern dem før udfyldningen, ellers viser feltet et hul og afviser værdier ét tegn tidligere, end brugeren forventer
Hvorfor skal bidi-omsortering ske, før celler tildeles?
HotPDF kører hele linjen gennem Unicode Bidirectional Algorithm (UAX #9) først og giver først derefter hver scalar sin celle ud fra dens visuelle position. At tildele celler i logisk orden og flippe resultatet bagefter virker for en ren arabisk streng og fejler, i samme øjeblik linjen indeholder cifre eller et latinsk token, for de stykker skal fortsat læses venstre til højre inde i den højre-til-venstre-linje. En værdi som en referencekode med arabisk præfiks og et nummer beholder sine cifre i rigtig orden kun, hvis omsorteringen ser hele linjen på én gang. Oversigten over complex-script text shaping går dybere ned i problemet med logisk versus visuel orden
Justeringen placerer derefter hele gitteret af brugte celler inde i feltet. Feltets nedarvede /Q-post vinder, når den er til stede; Options.Alignment gælder kun, når formularen ikke har nogen. Det betyder noget for højre-til-venstre-formularer: en kort arabisk værdi i et venstrejusteret comb-felt fylder de venstre bokse, hvilket er spec-korrekt og næsten aldrig, hvad formular-designeren havde tænkt sig. Ret formularens /Q eller sæt htaRight, når feltet ikke har egen quadding. Widgetrotation kommer fra /MK /R-posten, og ved 90 eller 270 grader bytter den logiske tekst-viewport bredde og højde, mens cellelogikken forbliver den samme
At udfylde et mixed-font comb-felt fra Delphi
Mixed-font comb-tekst bruger ét cellegitter, én fontstørrelse og én baseline for hver leveret font. Med FallbackFontFamilies sat til True vælger HotPDF en font til hvert komplet grafem ud fra de leverede familier i deres konfigurerede rækkefølge, så en arabisk face kan bære bogstaverne, mens en latinsk face bærer indlejret ASCII. Hvert fontspan shappes med hele linjen som kontekst, et mærke splittes aldrig fra sin base hen over fonte, og ingen ligatur opfindes hen over en fontgrænse. At lægge hvert span ud med sit eget lokale gitter eller skalere det til sin egen bounding box ville være enklere, og det ville knække både cellepositioner og roterede felter, i samme øjeblik fallback skiftede font midt i linjen
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); // én til seksten familier
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; // allerede default
Options.FallbackFontFamilies := True; // default er False
Options.FontSize := 0; // auto-fit, maks. 12
Options.Alignment := htaRight; // bruges kun, når /Q mangler
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
Doc.SaveToFile(TargetFile); // skal være forskellig fra SourceFile
finally
Doc.Free;
end;
end;
Bemærk, hvad eksemplet ikke sætter. Under dokumentudfyldning udledes Width, Height, Rotation og CombCells fra hver widgetrektangel, dens /MK-post og feltets MaxLen, så værdier, du lægger i de medlemmer, bliver overskrevet. En FontSize på nul beder om automatisk størrelse, som tjekker hvert clusters udstrækning mod bredden af dets k celler og den fælles metric- og ink-højde af alle deltagende fonte; en fast størrelse klippes blot til widgeten. CharacterSpacing og WordSpacing flytter aldrig comb-positioner, for gitteret alene bestemmer, hvor et cluster lander. HotPDF loader sine HarfBuzz- og FriBidi-runtimes kun, når en værdi faktisk kræver shaping, og værdier uden shaping-behov beholder det eksisterende direkte comb-layout uændret. Til det generelle loaded-PDF-udfyldningsworkflow, inklusiv tilladelser og gemning, se sætning af formularfeltværdier i en loadet PDF
Hvad garanterer et shappet comb-appearance ikke?
Et shappet comb-appearance garanterer korrekt celletildeling, intakte clusters og en bevaret logisk værdi; det garanterer ikke sømløse kursive sammenføjninger eller perfekt tekstekstraktion i hvert værktøj. Begge grænser følger af, hvad et comb-felt er, og du bør kende dem, før du lover en kunde noget
Kursive sammenføjninger hen over celler er den første. Arabiske bogstaver shappes stadig med hel-linje-kontekst, så hvert får sin korrekte initial-, medial- eller finalform, men det faste cellehulrum sætter plads mellem adskilte clusters, og forbindelsesstregene mødes ikke, som de gør i løbende tekst. Det er iboende i at tegne et sammenføjende skriftsystem ind i bokse, og ingen mængde centrering skjuler det
Tekstekstraktion er den anden. Feltets /V beholder den rene logiske værdi, og appearanceens parent Form XObject bærer ét /ActualText-span (ISO 32000-1 §14.9.4) med det komplette input; child Forms bærer ingen. Ekstraktorer, der ærer ActualText, genvinder den oprindelige streng. Ekstraktorer, der ignorerer den, genopbygger tekst ud fra glyph-positioner og kan returnere en anden orden eller duplikere cluster-tekst, for et multi-glyph cluster har ingen enkelt åbenbar Unicode-værdi per glyph. Har et downstream-system brug for værdien, så læs feltet, ikke sideteksten
De resterende grænser er mekaniske. Comb-tekst skal være én enkelt linje uden tabulatorer, og en værdi med flere scalarer end celler fejler i stedet for at blive trunkeret. Fejl er transaktionelle: et manglende glyph, en ugyldig font, en budgetoverskridelse eller en annullering efterlader den forrige værdi og appearances urørte, så en afvist udfyldning aldrig efterlader et halvtegnet felt
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;
// Ved enten fejl beholder feltet sin forrige værdi og sit appearance
Hvor shappede comb-felter passer ind i en formularpipeline
Shappet comb-understøttelse betyder mest for formularer, der allerede findes: selvangivelser, visa-ansøgninger, bank-onboardingpakker og ID-registreringer designet med boksede felter og nu udfyldt på arabisk, hebraisk, persisk eller urdu. Tæl i scalarer, behold den oprindelige værdi i /V, lad ét gitter styre hver font, og test med en værdi, der blander højre-til-venstre-bogstaver, cifre og et combining mark, for den ene streng gennemgår hver regel i denne artikel. Headless-dokument-API'en, comb shaping og font fallback beskrevet her er en del af HotPDF Delphi PDF component