HotPDF vyplňuje AcroForm comb pole arabským a hebrejským textem, combining marks a smíšenými směry přes THPDFHeadlessDocument.SetTextFieldValueWithFamilies. Comb mřížka se počítá v Unicode scalars, ligatura nebo základní písmeno se svými markami se centrují jako jeden cluster napříč k buňkám, které jejich scalars vlastní, a celý řádek projde Unicode Bidirectional Algorithm, ještě než se přidělí jediná buňka
Každé z těch tří pravidel nahrazuje zkratku, která v latinské ukázce vypadá dobře a na prvním reálném formuláři se rozpadne. Představte si vládní přihlášku s comb polem o deseti boxech na jméno psané arabsky. Vyplníte ji logikou po znacích a dostanete písmena v izolovaných tvarech pochodující zleva doprava, ligaturu lam-alef vestlanou do jednoho boxu, zatímco soused zůstává prázdný, a fathu vlající nad špatnou buňkou. Každou z těch chyb způsobí počítání špatné jednotky nebo řazení textu ve špatný okamžik
Co vlastně příznak Comb žádá po PDF writeru?
Příznak Comb žádá writer, aby pole rozdělil na přesně MaxLen stejně rozestavěných pozic a do každé uložil po jednom znaku. ISO 32000-1 §12.7.4.3 definuje Comb jako bitovou pozici 25 v Table 228, příznacích polí specifických pro textová pole, a dává smysl jen tehdy, když existuje položka MaxLen z Table 229 a příznaky Multiline, Password a FileSelect jsou vypnuté. Bit sám je $1000000 v 32bitové hodnotě /Ff; pokud jste někdy viděli comb pole blbnout po cestě přes proměnnou s plovoucí čárkou, článek o 32bitových AcroForm příznacích polí vysvětluje, proč tenhle bit mizí jako první
Specifikace říká „character" a končí. Psala se s latinskými boxy pevné šířky v hlavě a neříká, co je znak, když se dvojice arabských písmen stane jedinou glyfou nebo když samohlásková marka nemá žádnou vlastní šířku. HotPDF si musí vybrat jednotku a strukturní pravidla přitom vynucuje přísně: comb pole, které je zároveň multiline, nebo nemá kladné MaxLen, se odmítne s diagnostikou a password pole nesmí být comb polem vůbec
Proč počítat Unicode scalars místo UTF-16 jednotek, glyfů nebo graphemes?
HotPDF počítá comb buňky v Unicode scalars, protože to je tatáž jednotka, kterou vynucuje MaxLen, a mřížka s kontrolou délky se musí shodovat, jinak se hodnota, která projde validací, do svých boxů nevejde. Tři lákavé alternativy kazí tu shodu každá jinak
- UTF-16 code units je to, co vrací Delphi
Length, takže supplementární znak jako U+20BB7 by snědl dva boxy a dvě pozice rozpočtu pole - Počty glyfů závisí na fontu: většina arabských fontů dělá z lam plus alef jedinou glyfu, takže počet buněk by se změnil v okamžiku, kdy vyměníte písmo
- Počty graphemes slévají základní písmeno s jeho markami, takže mřížka by se rozcházela s kontrolou
MaxLenpočítající každý scalar, který uživatel napsal
Validace MaxLen v THPDFHeadlessDocument prochází řetězec jen tehdy, když jeho UTF-16 délka limit přesahuje, a když ano, platný surrogate pair počítá jednou a nepárovaný surrogate odmítá rovnou. Pokud chce vaše UI varovat před voláním místo chytání výjimky po něm, počítejte stejně
// Počítá Unicode scalars stejně, jako to dělá comb mřížka:
// platný surrogate pair je jeden 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;
Pár hodnot ukazuje, jak daleko se jednotky rozejdou a proč sloupci scalars jediné sedí to, na co comb pole dává rozpočet
| Hodnota | UTF-16 jednotky | Scalars (buňky) | Typické glyfy |
|---|---|---|---|
| Lam + alef (لا) | 2 | 2 | 1 ligatura |
| Beh + fatha (بَ) | 2 | 2 | 2, marka přichycená k základnímu písmenu |
| U+20BB7 (𠮷) | 2 | 1 | 1 |
| Decomposed e + U+0301 | 2 | 2 | 1 nebo 2, podle fontu |
Jak se ligatury a combining marks dělí o své buňky?
Shaped cluster pokrývající k scalars obsadí k sousedících buněk a centruje se jako jeden celek napříč nimi. Po shapingu se každý cluster zpětně namapuje na přesný úsek zdrojového textu, ze kterého vzešel, takže HotPDF ví, které scalars a tedy které buňky vlastní. Ligatura lam-alef výše dostane dva boxy a sedí centrovaná přes oba. Beh nesoucí shaddu a fathu je tři scalars, takže dostane tři boxy, přičemž marky zůstávají přichycené k písmenu
Centrování je místo, kde se zjevná implementace splete. Centrovení každé glyfy v jejím vlastním boxu odtrhne marku od základu, protože pozice marky má smysl jen relativně k písmenu, které zdobí. HotPDF nechává každou glyfu uvnitř clusteru na jejím shaped advance a jejích offsetech X a Y a pak cluster centruje podle kombinovaného horizontálního rozsahu přirozeného advancu, pozicovaných počátků glyf a skutečné inkoustové plochy glyf. Cluster se pohybuje v celku; nic uvnitř něj se nepřeskládává
Jeden důsledek jde snadno přehlédnout. Směrové řídicí znaky jako U+200F RIGHT-TO-LEFT MARK, U+061C ARABIC LETTER MARK a embedding a isolate kontrolní znaky jsou Unicode scalars bez glyfy. Počítají se do MaxLen a drží si buňku, která zůstane prázdná. Pokud vaše zdrojová data přicházejí ze systému, který tyhle marky do jmen sype, odstraňte je před vyplněním, jinak pole ukáže mezeru a hodnoty odmítne o znak dřív, než uživatel čeká
Proč musí přeřazení bidi proběhnout před přidělením buněk?
HotPDF pustí celý řádek nejdřív Unicode Bidirectional Algorithmem (UAX #9) a teprve potom dá každému scalaru jeho buňku podle vizuální pozice. Přidělování buněk v logickém pořadí a překlopení výsledku potom funguje u čistě arabského řetězce a selže, jakmile řádek obsahuje číslice nebo latinský token, protože tyhle úseky si uvnitř pravolevého řádku musí udržet čtení zleva doprava. Hodnota jako referenční kód s arabským prefixem a číslem drží číslice ve správném pořadí jen tehdy, když přeřazení vidí celý řádek najednou. Přehled shapingu komplexních písem rozebírá problém logického verzus vizuálního pořadí do hloubky
Zarovnání pak umístí celou mřížku použitých buněk dovnitř pole. Zděděná položka /Q pole vyhrává, když je přítomná; Options.Alignment se aplikuje jen tehdy, když ji formulář nemá. To má význam u formulářů psaných zprava doleva: krátká arabská hodnota v comb poli zarovnaném doleva zaplní nejlevější boxy, což je podle specifikace správně a skoro nikdy to není to, co autor formuláře zamýšlel. Opravte /Q formuláře nebo nastavte htaRight, když pole žádné quadding nemá. Rotace widgetu pochází z položky /MK /R a při 90 nebo 270 stupních si logický textový viewport vymění šířku s výškou, zatímco logika buněk zůstává stejná
Vyplnění comb pole se smíšenými fonty z Delphi
Comb text se smíšenými fonty používá jednu mřížku buněk, jednu velikost písma a jednu baseline pro každý dodaný font. S FallbackFontFamilies nastaveným na True vybírá HotPDF font pro každý kompletní grapheme z dodaných family v nakonfigurovaném pořadí, takže arabský řez nese písmena a latinský řez vložené ASCII. Každý fontový úsek se shapeuje s celým řádkem jako kontextem, marka se nikdy neoddělí od svého základu napříč fonty a ligatura se přes hranici fontů nevymýšlí. Rozvraset každý úsek vlastní lokální mřížkou nebo ho škálovat do vlastního bounding boxu by bylo jednodušší a rozbilo by to pozice buněk i otočená pole v okamžiku, kdy fallback přepne fonty uprostřed řádku
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); // jedna až šestnáct family
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; // už je default
Options.FallbackFontFamilies := True; // default je False
Options.FontSize := 0; // auto-fit, strop na 12
Options.Alignment := htaRight; // použije se jen bez /Q
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
Doc.SaveToFile(TargetFile); // musí se lišit od SourceFile
finally
Doc.Free;
end;
end;
Všimněte si, co příklad nenastavuje. Během plnění dokumentu se Width, Height, Rotation a CombCells odvozují z obdélníku každého widgetu, jeho položky /MK a MaxLen pole, takže hodnoty, které do těch členů napíšete, se přepíšou. Nulové FontSize žádá automatické rozměrování, které kontroluje rozsah každého clusteru proti šířce jeho k buněk a sdílené metrické a inkoustové výšce všech zúčastněných fontů; pevná velikost se jednoduše ořízne na widget. CharacterSpacing a WordSpacing comb pozice nikdy nepohnou, protože o tom, kde cluster dopadne, rozhoduje sama mřížka. HotPDF nahraje své runtimes HarfBuzz a FriBidi jen tehdy, když hodnota skutečně potřebuje shaping, a hodnoty, které shaping nepotřebují, si nechávají stávající přímý comb layout beze změny. Obecný workflow plnění načteného PDF, včetně oprávnění a ukládání, popisuje nastavování hodnot formulářových polí v načteném PDF
Co shaped podoba comb pole negarantuje?
Shaped podoba comb pole garantuje správné přidělení buněk, neporušené clustery a zachovanou logickou hodnotu; negarantuje plynulé kurzivní spoje ani dokonalou extrakci textu v každém nástroji. Obě omezení vyplývají z toho, co comb pole je, a měli byste je znát, než zákazníkovi slíbíte cokoliv
První jsou kurzivní spoje napříč buňkami. Arabská písmena se stále shapeují s kontextem celého řádku, takže každé dostane svůj správný tvar initial, medial nebo final, ale pevný rozestup buněk vloží mezeru mezi oddělené clustery a spojovací tahy se nepotkají tak, jak se potkávají v plynulém textu. To je vlastní kreslení spojovacího písma do boxů a žádné centrovení to nezakryje
Druhá je extrakce textu. Položka /V pole drží prostou logickou hodnotu a rodičovský Form XObject podoby nese jediný úsek /ActualText (ISO 32000-1 §14.9.4) s kompletním vstupem; podřízené Forms žádný nenosí. Extrakátory, které ActualText respektují, obnoví původní řetězec. Extrakátory, které ho ignorují, staví text znovu z pozic glyf a mohou vrátit jiné pořadí nebo zdvojený text clusteru, protože multi-glyph cluster nemá pro každý glyph jedinou zjevnou Unicode hodnotu. Pokud hodnotu potřebuje systém po proudu, čtěte pole, ne text stránky
Zbývající hranice jsou mechanické. Comb text musí být jediný řádek bez tabulátorů a hodnota s víc scalars než buněk selže místo ořezání. Selhání jsou transakční: chybějící glyfa, neplatný font, překročený rozpočet nebo zrušení nechá předchozí hodnotu a podoby nedotčené, takže odmítnuté vyplnění nikdy nezanechá napůl nakreslené pole
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;
// V obou chybových případech si pole nechává předchozí hodnotu a podobu
Kam shaped comb pole patří ve formulářové pipeline
Podpora shaped comb polí nejužitečnější je u formulářů, které už existují: daňová přiznání, vízové přihlášky, onboarding balíčky bank a registrace identit navržené s boxovanými poli a dnes vyplňované arabsky, hebrejsky, persky nebo urdusky. Počítejte ve scalars, držte původní hodnotu v /V, nechte jednu mřížku vládnout všem fontům a testujte hodnotou, která míchá pravolevá písmena, číslice a combining marku, protože jediný takový řetězec protáhne každé pravidlo z tohohle článku. Headless document API, comb shaping a font fallback popsané tady jsou součástí HotPDF Delphi PDF component