Modelarea textului în componenta PDFium trece printr-un singur obiect instalabil. ConfigureTextShaper instalează shaper-ul prin care fiecare punct de intrare de modelare se dirijează, înlocuind și eliberând orice era acolo; ActiveTextShaper returnează cel instalat și creează implicitul platformei la prima folosire; ActiveTextShaperName raportează ce backend este activ; ClearTextShaper renunță la instalare și lasă implicitul să fie creat din nou. Pe Windows implicitul este TPdfUniscribeTextShaper. Sub Free Pascal există TPdfHarfBuzzTextShaper, care leagă libharfbuzz la execuție, astfel încât o bibliotecă lipsă este o condiție raportată, nu un eșec de încărcare
O interfață, două backend-uri care împart munca complet diferit. Înțelegerea acelei asimetrii este ceea ce împiedică calea portabilă să producă text modelat corect și poziționat greșit
De ce backend-ul Windows este o clasă, iar cel portabil trei bucăți?
Pentru că Uniscribe sunt patru API-uri care se prefac a fi unul. ScriptItemize segmentează un șir după script și rezolvă nivelurile bidirecționale; ScriptShape cartografiază caractere la glife; ScriptPlace calculează avansurile și offset-urile; ScriptLayout pune trecerile rezultate în ordine vizuală. Un backend construit pe el nu mai are deci nimic de adăugat, ceea ce este motivul pentru care shaper-ul Windows este o singură clasă cu o singură metodă
HarfBuzz acoperă cele două din mijloc. El modelează și plasează o trecere a cărei direcție și script au fost deja decise de apelant, și nu are nicio părere despre cum se împarte un paragraf în treceri sau în ce ordine apar acele treceri. Deci backend-ul portabil furnizează restul: algoritmul bidirecțional rezolvă nivelurile de înglobare, funcțiile Unicode HarfBuzz segmentează textul după script, iar trecerile sunt aranjate în ordinea vizuală pe care o produce regula L2 din UAX #9. Jumătatea bidirecțională este suficient de substanțială încât să fie o unitate proprie, descrisă în articolul despre nivelurile de înglobare UAX #9
Shaper-ul nu rezolvă fonturi, și asta este deliberat
Uniscribe citește binarul fontului dintr-un context de dispozitiv GDI. Nu există un echivalent portabil pentru asta, iar inventarea unuia în interiorul unei unități de modelare ar însemna a decide, în numele fiecărei aplicații, dacă fonturile vin din fontconfig, din CoreText, dintr-un dosar de fonturi al aplicației sau dintr-o bază de date. Deci backend-ul HarfBuzz primește un resolver: un callback care cartografiază un nume de font la octeții TrueType sau OpenType. Returnarea lui False face cererea de modelare să eșueze în același fel în care o face un font GDI ilizibil pe Windows
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// Politica dumneavoastră: fontconfig, CoreText, un dosar de fonturi
// al aplicației, o bază de date
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// Proprietatea trece la unitate; apelați o dată la pornire,
// înainte ca ceva să modeleze text
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// Pe Delphi implicitul platformei (Uniscribe) este creat la cerere,
// deci nu este necesară deloc o instalare
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
Păstrarea descoperirii de fonturi în afara shaper-ului are un al doilea beneficiu care apare în servere: același proces poate modela cu un set de fonturi înglobat care nu are nimic de-a face cu ceea ce este instalat pe mașină, ceea ce vreți când rezultatul trebuie să fie reproductibil octet cu octet între gazde. Componenta expune de asemenea un furnizor de fonturi de sistem gazdă pentru cazurile în care vreți într-adevăr fonturile instalate, tratat în articolul despre furnizorul de fonturi de sistem
Înregistrarea de rezultat este neutru față de backend, iar clusterele sunt motivul
Ambele backend-uri umple același TPdfShapedText: textul sursă, numele fontului, mărimea, octeții fontului, un vector de treceri, lățimea totală, numărul de glife și numărul de caractere logice. Fiecare TPdfShapedRun poartă segmentul său în textul sursă, poziția sa vizuală X, lățimea sa, nivelul său bidirecțional și un fanion dreapta-stânga, plus glifele lui. Fiecare TPdfShapedGlyph poartă un identificator de glifă, un avans, offset-uri X și Y, și clusterul căruia îi aparține ca un început și o lungime în textul sursă
Câmpurile de cluster acelea sunt ceea ce face înregistrarea folosibilă, nu doar informativă. Modelarea nu este o cartografiere unu-la-unu: o silabă Devanagari devine o glifă din patru caractere, o ligatură arabă îmbină două, iar un singur caracter poate produce mai multe semne. Fără segmente de cluster nu puteți plasa un cursor, nu puteți testa la atingere un clic sau evidenția o selecție, deoarece nu puteți spune cărui caracter aparține o glifă. Cu ele, aritmetica este locală, iar același cod funcționează pentru ambele backend-uri
var
Shaped: TPdfShapedText;
R, G: Integer;
begin
if ShapePdfText(Line, 'Noto Sans Arabic', 14, ptdAuto, Shaped) then
for R := 0 to High(Shaped.Runs) do
begin
// Trecerile sosesc deja în ordine vizuală cu VisualX completat
X := Shaped.Runs[R].VisualX;
for G := 0 to High(Shaped.Runs[R].Glyphs) do
begin
EmitGlyph(Shaped.Runs[R].Glyphs[G].GlyphID,
X + Shaped.Runs[R].Glyphs[G].OffsetX,
Shaped.Runs[R].Glyphs[G].OffsetY);
X := X + Shaped.Runs[R].Glyphs[G].Advance;
end;
end;
end;
Bugetele aparțin înregistrării de opțiuni
TPdfTextShapingOptions poartă o direcție plus trei limite: maxim de caractere, maxim de glife și maxim de treceri, cu o funcție de clasă Default care umple valori sensibile. Limitele nu sunt paranoie față de date de intrare malformate; sunt aritmetică. Modelarea expandează: un font cu substituție contextuală agresivă poate emite mai multe glife decât caractere de intrare, iar un paragraf care alternează scripturile la fiecare câteva caractere produce o trecere per comutare. Un document asamblat pentru a maximiza ambele transformă un șir modest într-o alocare mare, iar un serviciu care modelează text din PDF-uri nesigure are nevoie de o limită pe care a ales-o, nu de una pe care o impune mașina
Stabilirea direcției explicit, în loc să o lăsați pe automat, merită făcută ori de câte ori o știți deja. Automatul aplică regulile de direcție de paragraf pentru a ghici de la primul caracter puternic, ceea ce este corect pentru text liber și greșit pentru un câmp de formular a cărui direcție este o proprietate a câmpului, nu a valorii pe care cineva a bătut-o în el
Legare la execuție, nu o dependență de build
Backend-ul HarfBuzz încarcă biblioteca dinamic. Aceasta este o decizie de implementare cu consecințe reale: un singur binar rulează pe o mașină cu HarfBuzz și pe una fără, raportând capacitate redusă în al doilea caz în loc să nu pornească. Pentru o bibliotecă livrată altor dezvoltatori acesta este singurul aranjament lucrativ, deoarece nu puteți cere fiecărui consumator al unei componente PDF să achiziționeze și să coreleze versiunile unei biblioteci de modelare de care s-ar putea să nu aibă nevoie
Regula corespunzătoare pentru apelanți este să verificați. ActiveTextShaper returnează nil când platforma nu are implicit și niciunul nu a fost configurat, iar punctul de intrare de modelare raportează asta drept shaper indisponibil, nu ca eșec de modelare. Acestea sunt probleme diferite și merită mesaje diferite: una este o lacună de implementare, cealaltă este o problemă de font sau de text
Instalați o dată, înainte ca ceva să modeleze
Instalarea înlocuiește și eliberează shaper-ul anterior, deci a o apela repetat este sigur dar inutil, iar a o apela în timp ce alt fir modelează nu este deloc sigur. Faceți-o în timpul pornirii. Dacă aveți nevoie să reveniți la implicitul platformei mai târziu, transmiteți nil, ceea ce este și felul de a anula un dublur de test la finalul unui test
Odată instalat un backend, măsurarea și împachetarea se comportă la fel pe ambele platforme, deoarece consumă metricile de treceri și glife în loc să apeleze direct platforma; modelul de împachetare este descris în articolul despre măsurarea textului și împachetarea pe cuvinte. Platformele și toolchain-urile suportate pentru componentă sunt listate pe pagina de produs PDFium Delphi component