HotPDF fyller AcroForm comb-fält med arabiska, hebreiska, kombinerande märken och blandad-riktning-text via THPDFHeadlessDocument.SetTextFieldValueWithFamilies. Comb-rutnätet räknas i Unicode-scalars, en ligatur eller en basbokstav med sina märken centreras som ett kluster över de k celler dess scalars äger, och hela raden kör igenom Unicode Bidirectional Algorithm innan någon cell tilldelas
Var och en av de tre reglerna ersätter en genväg som ser fin ut i en latinsk demo och faller sönder på det första riktiga formuläret. Föreställ dig en myndighetsansökan med ett comb-fält i tio rutor för ett namn skrivet på arabiska. Fyll det med per-tecken-logik och du får bokstäver i isolerade former som marscherar vänster till höger, en lam-alef-ligatur pressad i en ruta medan dess granne står tom, och en fatha som svävar över fel cell. Vart och ett av de felen kommer från att räkna fel enhet eller ordna texten vid fel ögonblick
Vad berättar Comb-flaggan egentligen för en PDF-skrivare?
Comb-flaggan ber skrivaren dela fältet i exakt MaxLen lika fördelade positioner och sätta ett tecken i varje. ISO 32000-1 §12.7.4.3 definierar Comb som bitposition 25 i Table 228, fältflaggorna specifika för textfält, och gör den meningsfull bara när MaxLen-posten från Table 229 finns och Multiline-, Password- och FileSelect-flaggorna är rensade. Biten själv är $1000000 i det 32-bitars /Ff-värdet; har du någonsin sett comb-fält bete sig konstigt efter en roundtrip genom en flyttalsvariabel förklarar artikeln om 32-bitars AcroForm-fältflaggor varför just den biten går först
Specifikationen säger ”tecken” och stannar där. Den skrevs med latinska boxar i fast bredd i åtanke och säger inte vad ett tecken är när ett arabiskt bokstavspar blir en glyph, eller när ett vokalmärke inte har egen bredd. HotPDF måste välja en enhet, och den driver de strukturella reglerna strikt medan det sker: ett comb-fält som också är multiline, eller som bär ingen positiv MaxLen, avvisas med en diagnos, och lösenordsfält kan inte vara comb-fält alls
Varför räkna Unicode-scalars i stället för UTF-16-enheter, glypher eller grafemer?
HotPDF räknar comb-celler i Unicode-scalars för det är samma enhet den använder för att driva MaxLen, och rutnätet och längdkontrollen måste vara överens annars passar inte ett värde som klarar valideringen sina rutor. De tre frestande alternativen bryter var och en den överenskommelsen på olika sätt
- UTF-16-kodenheter är vad Delphis
Lengthreturnerar, så ett supplerande tecken som U+20BB7 skulle äta två rutor och två platser av fältbudgeten - Glyph-antal beror på fonten: de flesta arabiska fonts gör lam plus alef till en glyph, så cellantalet skulle ändras i samma stund du byter font
- Grafem-antal slår ihop en basbokstav med sina märken, vilket betyder att rutnätet skulle säga emot en
MaxLen-kontroll som räknar varje scalar användaren skrev
MaxLen-valideringen i THPDFHeadlessDocument går bara igenom strängen när dess UTF-16-längd överskrider gränsen, och när den gör det räknar den ett giltigt surrogatpar en gång och avvisar ett oparat surrogat rakt av. Vill ditt UI varna före anropet i stället för att fånga ett exception efteråt, räkna på samma sätt
// Räknar Unicode-scalars som comb-rutnätet gör:
// ett giltigt surrogatpar är en 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;
Några värden visar hur långt enheterna glider isär, och varför bara scalar-kolumnen stämmer med vad ett comb-fält budgeterar för
| Värde | UTF-16-enheter | Scalars (celler) | Typiska glypher |
|---|---|---|---|
| Lam + alef (لا) | 2 | 2 | 1 ligatur |
| Beh + fatha (بَ) | 2 | 2 | 2, märke fäst vid basen |
| U+20BB7 (𠮷) | 2 | 1 | 1 |
| Decomposed e + U+0301 | 2 | 2 | 1 eller 2, fontberoende |
Hur delar ligaturer och kombinerande märken sina celler?
Ett format kluster som täcker k scalars upptar k sammanhängande celler och centreras som en enda enhet över dem. Efter shaping mappas varje kluster tillbaka till den exakta spännvidden av källtext den kom från, så HotPDF vet vilka scalars, och alltså vilka celler, den äger. Lam-alef-ligaturen ovan får två rutor och sitter centrerad över båda. En beh som bär en shadda och en fatha är tre scalars, så den får tre rutor, med märkena fortfarande fästa vid bokstaven
Centreringen är där den uppenbara implementationen går snett. Att centrera varje glyph i sin egen ruta drar ett märke från sin bas, för märkets position betyder bara något i förhållande till bokstaven den smyckar. HotPDF behåller varje glyph inuti ett kluster på sin formade advans och sina X- och Y-offset, och centrerar sedan klustret med den kombinerade horisontella utsträckningen av den naturliga advansen, de positionerade glyph-origo och glyphens faktiska bläck. Klustret flyttar sig som ett block; ingenting inuti det arrangeras om
En konsekvens är lätt att missa. Riktningskontroller som U+200F RIGHT-TO-LEFT MARK, U+061C ARABIC LETTER MARK, och embedding- och isolate-kontrollerna är Unicode-scalars utan glyph. De räknas mot MaxLen och håller en cell som förblir tom. Kommer din källdata från ett system som strör de här märkena in i namn, ta bort dem före fyllningen, annars visar fältet ett gap och avvisar värden ett tecken tidigare än användaren väntar sig
Varför måste bidi-omordning ske innan celler tilldelas?
HotPDF kör hela raden genom Unicode Bidirectional Algorithm (UAX #9) först, och först därefter ger varje scalar sin cell från sin visuella position. Att tilldela celler i logisk ordning och vända resultatet efteråt funkar för en ren arabisk sträng och faller så snart raden innehåller siffror eller en latinsk token, för de körningarna måste fortsätta läsas vänster till höger inuti höger-till-vänster-raden. Ett värde som en referenskod med arabiskt prefix och ett nummer håller sina siffror i rätt ordning bara om omordningen ser hela raden på en gång. Översikten om complex-script textshaping täcker problemet logisk-mot-visuell ordning mer i djupet
Justering positionerar sedan hela rutnätet av använda celler inuti fältet. Fältets ärvda /Q-post vinner när den finns; Options.Alignment gäller bara när formuläret saknar en. Det spelar roll för höger-till-vänster-formulär: ett kort arabiskt värde i ett vänsterjusterat comb-fält fyller de vänstraste rutorna, vilket är spec-korrekt och nästan aldrig vad formulärdesignern avsåg. Fixa formulärets /Q, eller sätt htaRight när fältet saknar egen quadding. Widget-rotation kommer från /MK-posten /R, och vid 90 eller 270 grader byter den logiska textvisningsytan plats på bredd och höjd medan celllogiken förblir densamma
Fylla ett blandad-font comb-fält från Delphi
Blandad-font comb-text använder ett cellrutnät, en fontstorlek och en baslinje för varje levererad font. Med FallbackFontFamilies satt till True plockar HotPDF en font för varje komplett grafem ur de levererade familjerna i deras konfigurerade ordning, så ett arabiskt ansikte kan bära bokstäverna medan ett latinskt ansikte bär inbäddad ASCII. Varje fontspan formeras med hela raden som kontext, ett märke delas aldrig från sin bas över fonts, och ingen ligatur hittas på över en fontgräns. Att lägga ut varje span med sitt eget lokala rutnät eller skala det till sin egen bounding box vore enklare, och det skulle krossa både cellpositioner och roterade fält i samma stund fallbacken bytte font mitt på raden
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); // en till sexton familjer
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; // redan standard
Options.FallbackFontFamilies := True; // standard är False
Options.FontSize := 0; // autoanpassning, tak 12
Options.Alignment := htaRight; // används bara när /Q saknas
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
Doc.SaveToFile(TargetFile); // måste skilja sig från SourceFile
finally
Doc.Free;
end;
end;
Notera vad exemplet inte sätter. Under dokumentfyllning härleds Width, Height, Rotation och CombCells från varje widgetrektangel, dess /MK-post och fältets MaxLen, så värden du stoppar in i de medlemmarna skrivs över. En FontSize noll ber om automatisk storlek, som kontrollerar varje klusters utsträckning mot bredden av dess k celler och den delade metriken och bläckhöjden hos alla deltagande fonts; en fast storlek klipps helt enkelt till widgeten. CharacterSpacing och WordSpacing flyttar aldrig comb-positioner, eftersom rutnätet ensamt avgör var ett kluster landar. HotPDF laddar sina HarfBuzz- och FriBidi-runtimes bara när ett värde faktiskt kräver shaping, och värden som inte behöver shaping behåller den befintliga direkta comb-layouten oförändrad. För det allmänna fyllningsarbetsflödet i laddad PDF, inklusive behörigheter och sparning, se att sätta formulärfältvärden i en laddad PDF
Vad kan du inte räkna med från ett format comb-utseende?
Ett format comb-utseende garanterar korrekt cellstilldelning, intakta kluster och ett bevarat logiskt värde; det garanterar inte sömlösa kursiva övergångar eller perfekt textextrahering i varje verktyg. Båda gränserna följer av vad ett comb-fält är, och du bör känna till dem innan du utlovar något till en kund
Kursiva övergångar över celler är den första. Arabiska bokstäver formas fortfarande med helrads-kontext, så var och en får sin korrekta initial-, medial- eller finalform, men det fasta cellsteget lägger mellanrum mellan separata kluster och förbindelsestrecken möts inte som de gör i löpande text. Det är inneboende i att rita ett förbindande skriftsystem i boxar, och ingen mängd centrering döljer det
Textextrahering är den andra. Fältets /V behåller det rena logiska värdet, och utseendets förälder Form XObject bär en /ActualText-span (ISO 32000-1 §14.9.4) med hela inmatningen; barn-Formsen bär ingen. Extraktorer som hedrar ActualText återvinner originalsträngen. Extraktorer som ignorerar den bygger texten på nytt från glyph-positioner och kan returnera en annan ordning eller duplicera klustertext, för ett kluster med flera glypher har inget enda uppenbart Unicode-värde per glyph. Behöver ett nedströmssystem värdet, läs fältet, inte sidtexten
De kvarvarande gränserna är mekaniska. Comb-text måste vara en enda rad utan tabbar, och ett värde med fler scalars än celler faller i stället för att trunkeras. Fel är transaktionella: en saknad glyph, en ogiltig font, en budgetöverskridning eller en avbrytning lämnar det tidigare värdet och utseendena orörda, så en avvisad fyllning lämnar aldrig ett halvritat fält efter sig
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;
// Vid något av felen behåller fältet sitt tidigare värde och utseende
Var formade comb-fält passar in i en formulärpipeline
Stöd för formade comb-fält spelar störst roll för formulär som redan finns: deklarationer, visumansökningar, bank-onboarding-paket och ID-registreringar designade med boxade fält och nu fyllda på arabiska, hebreiska, persiska eller urdu. Räkna i scalars, behålla originalvärdet i /V, låt ett rutnät styra varje font, och testa med ett värde som blandar höger-till-vänster-bokstäver, siffror och ett kombinerande märke, för den ensamma strängen tränar varje regel i den här artikeln. Headless-dokument-API:et, comb-shaping och font-fallback som beskrivs här ingår i HotPDF Delphi PDF-komponenten