HotPDF wypełnia pola comb AcroForm tekstem arabskim, hebrajskim, znakami łączącymi i tekstem o mieszanych kierunkach przez THPDFHeadlessDocument.SetTextFieldValueWithFamilies. Siatka comb jest liczona w skalarach Unicode, ligatura albo litera podstawowa ze swoimi znakami jest centrowana jako jeden klaster na k komórkach, które należą do jej skalarów, a cały wiersz przechodzi przez Unicode Bidirectional Algorithm, zanim jakakolwiek komórka zostanie przydzielona
Każda z tych trzech reguł zastępuje skrót, który wygląda nieźle w łacińskim demo i rozpada się na pierwszym prawdziwym formularzu. Wyobraź sobie wniosek rządowy z polem comb o dziesięciu kratkach na nazwisko zapisane po arabsku. Wypełnij go logiką per znak i dostaniesz litery w formach izolowanych maszerujące od lewej do prawej, ligaturę lam-alef wciśniętą w jedną kratkę, gdy sąsiednia stoi pusta, i fathę unoszącą się nad złą komórką. Każda z tych wad bierze się z liczenia złej jednostki albo porządkowania tekstu w złym momencie
Czego flaga Comb faktycznie żąda od writera PDF?
Flaga Comb żąda podzielenia pola na dokładnie MaxLen równoodległych pozycji i włożenia jednego znaku w każdą. ISO 32000-1 §12.7.4.3 definiuje Comb jako pozycję bitu 25 w Table 228, flag pól właściwych polom tekstowym, i czyni ją znaczącą tylko wtedy, gdy obecny jest wpis MaxLen z Table 229, a flagi Multiline, Password i FileSelect są wygaszone. Sam bit to $1000000 w 32-bitowej wartości /Ff; jeśli widziałeś kiedyś pola comb zachowujące się dziwnie po podróży przez zmienną zmiennoprzecinkową, artykuł o 32-bitowych flagach pól AcroForm wyjaśnia, dlaczego ten bit ginie pierwszy
Specyfikacja mówi „character" i na tym koniec. Była pisana z myślą o łacińskich kratkach o stałym skoku i nie mówi, czym jest znak, gdy para liter arabskich staje się jednym glifem albo gdy znak samogłoski nie ma własnej szerokości. HotPDF musi wybrać jednostkę i robi to, pilnując surowo reguł strukturalnych: pole comb, które jest jednocześnie multiline albo nie niesie dodatniego MaxLen, jest odrzucane z diagnostyką, a pola hasła nie mogą być polami comb w ogóle
Dlaczego liczyć skalary Unicode, a nie jednostki UTF-16, glify albo grafemy?
HotPDF liczy komórki comb w skalarach Unicode, bo to ta sama jednostka, którą egzekwuje MaxLen, a siatka i kontrola długości muszą się zgadzać, inaczej wartość przechodząca walidację nie zmieści się w swoich kratkach. Trzy kuszące alternatywy łamią tę zgodność każda po swojemu
- Jednostki kodowe UTF-16 to to, co zwraca
Lengthw Delphi, więc znak uzupełniający jak U+20BB7 zjadłby dwie kratki i dwa sloty budżetu pola - Liczniki glifów zależą od fontu: większość fontów arabskich robi z lam plus alef jeden glif, więc liczba komórek zmieniłaby się w chwili zmiany kroju
- Liczniki grafemów sklejają literę podstawową z jej znakami, więc siatka rozjechałaby się z kontrolą
MaxLenliczącą każdy skalar wpisany przez użytkownika
Walidacja MaxLen w THPDFHeadlessDocument przechodzi przez string tylko wtedy, gdy jego długość UTF-16 przekracza limit, i robiąc to, liczy poprawną parę surrogate raz, a samotny surrogate odrzuca wprost. Jeśli twój UI chce ostrzec przed wywołaniem zamiast łapać wyjątek po nim, licz tak samo
// Liczy skalary Unicode tak jak siatka comb:
// poprawna para surrogate to jeden skalar
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;
Kilka wartości pokazuje, jak daleko jednostki się rozjeżdżają i dlaczego tylko kolumna skalarów pasuje do tego, na co pole comb budżetuje
| Wartość | jednostki UTF-16 | skalary (komórki) | typowe glify |
|---|---|---|---|
| Lam + alef (لا) | 2 | 2 | 1 ligatura |
| Beh + fatha (بَ) | 2 | 2 | 2, znak przy podstawie |
| U+20BB7 (𠮷) | 2 | 1 | 1 |
| Decomposed e + U+0301 | 2 | 2 | 1 albo 2, zależnie od fontu |
Jak ligatury i znaki łączące dzielą swoje komórki?
Ukształtowany klaster obejmujący k skalarów zajmuje k sąsiadujących komórek i jest centrowany jako jedna całość na nich. Po shapingu każdy klaster jest mapowany z powrotem na dokładny wycinek tekstu źródłowego, z którego przyszedł, więc HotPDF wie, które skalary — a więc i które komórki — do niego należą. Ligatura lam-alef z przykładu wyżej dostaje dwie kratki i siedzi wycentrowana na obu. Beh z shaddą i fathą to trzy skalary, więc dostaje trzy kratki, ze znakami wciąż przypiętymi do litery
Centrowanie to moment, w którym oczywista implementacja się wykrzacza. Centrowanie każdego glifu we własnej kratce odrywa znak od podstawy, bo pozycja znaku znaczy coś tylko względem litery, którą zdobi. HotPDF trzyma każdy glif wewnątrz klastra przy jego ukształtowanym skoku i offsetach X i Y, potem centruje klaster używając połączonego zakresu poziomego naturalnego skoku, ustawionych originów glifów i faktycznego tuszu glifów. Klaster rusza się jako blok; nic wewnątrz niego nie jest przestawiane
Jedna konsekwencja łatwo umyka. Sterujące kierunkiem jak U+200F RIGHT-TO-LEFT MARK, U+061C ARABIC LETTER MARK oraz kontrolki embedding i isolate to skalary Unicode bez glifu. Liczą się do MaxLen i trzymają komórkę, która zostaje pusta. Jeśli twoje dane źródłowe pochodzą z systemu sypiącego tymi znakami po nazwiskach, obetnij je przed wypełnianiem, bo pole pokaże lukę i odrzuci wartości o znak wcześniej, niż użytkownik oczekuje
Dlaczego uporządkowanie bidi musi nastąpić przed przydziałem komórek?
HotPDF przepuszcza cały wiersz przez Unicode Bidirectional Algorithm (UAX #9) najpierw i dopiero potem daje każdemu skalarowi komórkę z jego pozycji wizualnej. Przydzielanie komórek w porządku logicznym i odwracanie wyniku potem działa dla czysto arabskiego stringa i pada, gdy tylko wiersz zawiera cyfry albo łaciński token, bo te biegi muszą zachować czytanie od lewej do prawej wewnątrz wiersza od prawej do lewej. Wartość typu kod referencji z arabskim prefiksem i numerem trzyma cyfry we właściwej kolejności tylko wtedy, gdy uporządkowanie widzi cały wiersz naraz. Przegląd shapingu tekstu skomplikowanych skryptów schodzi głębiej w problem porządku logicznego kontra wizualnego
Wyrównanie potem pozycjonuje całą siatkę użytych komórek wewnątrz pola. Dziedziczony wpis /Q pola wygrywa, gdy jest obecny; Options.Alignment aplikuje się tylko, gdy formularz go nie ma. To ma znaczenie dla formularzy od prawej do lewej: krótka wartość arabska w polu comb wyrównanym do lewej zapełnia skrajnie lewe kratki, co jest zgodne ze specyfikacją i prawie nigdy nie jest tym, o co chodziło projektantowi formularza. Napraw /Q formularza albo ustaw htaRight, gdy pole nie ma własnego quaddingu. Obrót widgeta pochodzi z wpisu /MK /R, a przy 90 albo 270 stopniach logiczny viewport tekstu zamienia szerokość z wysokością, podczas gdy logika komórek zostaje ta sama
Wypełnianie pola comb o mieszanych fontach z Delphi
Tekst comb o mieszanych fontach używa jednej siatki komórek, jednego rozmiaru fontu i jednej linii bazowej dla każdego podanego fontu. Z FallbackFontFamilies ustawionym na True HotPDF dobiera font dla każdego kompletnego grafemu z podanych rodzin w ich skonfigurowanej kolejności, więc arabski krój może nieść litery, a łaciński osadzone ASCII. Każdy bieg fontowy jest kształtowany z całym wierszem jako kontekstem, znak nigdy nie jest odrywany od podstawy między fontami i żadna ligatura nie jest wymyślana przez granicę fontu. Układanie każdego biegu we własnej lokalnej siatce albo skalowanie go do własnego bounding boxa byłoby prostsze i połamałoby i pozycje komórek, i obrócone pola w chwili, gdy fallback przełączy font w połowie wiersza
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); // od jednej do szesnastu rodzin
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; // i tak domyślne
Options.FallbackFontFamilies := True; // domyślnie False
Options.FontSize := 0; // auto-dopasowanie, limit 12
Options.Alignment := htaRight; // używane tylko przy braku /Q
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
Doc.SaveToFile(TargetFile); // musi się różnić od SourceFile
finally
Doc.Free;
end;
end;
Zauważ, czego przykład nie ustawia. Przy wypełnianiu dokumentu Width, Height, Rotation i CombCells są wyprowadzane z każdego prostokąta widgeta, jego wpisu /MK i MaxLen pola, więc wartości, które wstawisz w te pola, są nadpisywane. Zerowe FontSize prosi o automatyczne dobranie rozmiaru, które sprawdza zakres każdego klastra wobec szerokości jego k komórek i wspólnej metryki oraz wysokości tuszu wszystkich uczestniczących fontów; stały rozmiar jest po prostu przycinany do widgeta. CharacterSpacing i WordSpacing nigdy nie ruszają pozycji comb, bo o tym, gdzie ląduje klaster, decyduje sama siatka. HotPDF ładuje swoje runtime HarfBuzz i FriBidi tylko, gdy wartość faktycznie wymaga shapingu, a wartości niepotrzebujące shapingu zachowują istniejący bezpośredni układ comb bez zmian. Ogólny workflow wypełniania wczytanego PDF-a, łącznie z uprawnieniami i zapisem, opisuje ustawianie wartości pól formularza we wczytanym PDF-ie
Czego ukształtowany wygląd comb nie gwarantuje?
Ukształtowany wygląd comb gwarantuje poprawny przydział komórek, nietknięte klastry i zachowaną wartość logiczną; nie gwarantuje bezszwowych połączeń kursywnych ani idealnej ekstrakcji tekstu w każdym narzędziu. Oba limity wynikają z tego, czym pole comb jest, i warto je znać, zanim cokolwiek obiecasz klientowi
Pierwsze są połączenia kursywne przez komórki. Litery arabskie są nadal kształtowane z kontekstem całego wiersza, więc każda dostaje właściwą formę początkową, środkową albo końcową, ale stały skok komórek wkłada odstęp między osobne klastry i kreski łączące nie spotkają się tak, jak w płynnym tekście. To nieodłączna cecha rysowania skryptu łączonego w kratkach i żadne centrowanie tego nie ukryje
Drugą jest ekstrakcja tekstu. /V pola trzyma goły porządek logiczny, a rodzic Form XObject wyglądu niesie jeden bieg /ActualText (ISO 32000-1 §14.9.4) z kompletnym wejściem; formy potomne nie niosą niczego. Ekstraktory honorujące ActualText odzyskają oryginalny string. Ekstraktory je ignorujące składają tekst z pozycji glifów i mogą zwrócić inną kolejność albo zdublowany tekst klastra, bo klaster wieloglifowy nie ma jednej oczywistej wartości Unicode na glif. Jeśli system downstream potrzebuje wartości, czytaj pole, nie tekst strony
Pozostałe granice są mechaniczne. Tekst comb musi być jednym wierszem bez tabulatorów, a wartość o więcej skalarach niż komórek zawodzi zamiast zostać obcięta. Porażki są transakcyjne: brakujący glif, nieważny font, przekroczenie budżetu albo anulowanie zostawiają poprzednią wartość i wygląd nietknięte, więc odrzucone wypełnienie nigdy nie zostawia za sobą pół narysowanego pola
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;
// Przy obu błędach pole zachowuje poprzednią wartość i wygląd
Gdzie ukształtowane pola comb pasują do pipeline'u formularzy
Wsparcie ukształtowanych pól comb znaczy najwięcej dla formularzy, które już istnieją: zeznania podatkowe, wnioski wizowe, zestawy onboardingu bankowego i rejestracje dokumentów tożsamości zaprojektowane z polami w kratkach, teraz wypełniane po arabsku, hebrajsku, persku albo urdu. Licz w skalarach, trzymaj oryginalną wartość w /V, pozwól jednej siatce rządzić każdym fontem i testuj wartością mieszającą litery od prawej do lewej, cyfry i znak łączący, bo ten jeden string ćwiczy każdą regułę z tego artykułu. Headless API dokumentu, shaping comb i fallback fontów opisane tutaj są częścią komponentu HotPDF Delphi PDF