Tehnički članak

Oblikovanje teksta: Uniscribe i HarfBuzz u Delphi-ju

Oblikovanje teksta u PDFium komponenti ide kroz jedan instalabilan objekat. ConfigureTextShaper instalira oblikovača kroz koji svaka ulazna tačka oblikovanja ide, zamenjujući i oslobađajući šta god je tamo bilo; ActiveTextShaper vraća instaliranog i stvara platformski podrazumevani pri prvoj upotrebi; ActiveTextShaperName izveštava koji backend je živ; ClearTextShaper ispušta instalaciju i dopušta da se podrazumevani ponovo stvori. Na Windows-u podrazumevani je TPdfUniscribeTextShaper. Pod Free Pascal-om postoji TPdfHarfBuzzTextShaper, koji vezuje libharfbuzz u vremenu izvršavanja pa je nedostajuća biblioteka prijavljeno stanje umesto neuspeha učitavanja

Arhitektura priključivog oblikovanja teksta u PDFium Delphi komponenti: ConfigureTextShaper, ActiveTextShaper i ClearTextShaper upravljaju jednim instaliranim backend-om, Uniscribe na Windows-u i HarfBuzz vezan u vremenu izvršavanja pod Free Pascal-om
Svaki poziv oblikovanja ide kroz jedini instalirani objekat oblikovača, sa platformskim podrazumevanim na svakom cilju

Jedno sučelje, dva backend-a koja dele posao potpuno različito. Razumevanje te asimetrije ono je što sprečava prenosiv put da proizvodi tekst oblikovan ispravno a pozicioniran pogrešno

Zašto je Windows backend jedna klasa a prenosivi tri dela?

Zato je Uniscribe četiri API-ja koji se pretvaraju da su jedan. ScriptItemize segmenira niz znakova po pismu i razrešuje dvosmjerne nivoe; ScriptShape mapira znakove u glifove; ScriptPlace računa napredovanja i ofsete; ScriptLayout stavlja rezultujuće nizove u vizuelni redosled. Backend izgrađen na njemu dakle nema više šta dodati, što je razlog što je Windows oblikovač jedna klasa sa jednom metodom

HarfBuzz pokriva srednja dva. Oblikuje i postavlja niz čije su smer i pismo koje je pozivalac već odlučio, i nema mišljenje o tome kako se pasus cepa u nizove ni u kojem redosledu se ti nizovi pojavljuju. Pa prenosivi backend snabdeva ostalo: dvosmjerni algoritam razrešuje nivoe ugrađivanja, HarfBuzz Unicode funkcije segmentiraju tekst po pismu, i nizovi se slažu u vizuelnom redosledu koji pravilo L2 UAX #9 proizvodi. Dvosmjerna polovina dovoljno je obimna da bude sopstvena jedinica, opisana u članku o UAX #9 nivoima ugrađivanja

Poređenje pipeline-a oblikovanja za PDF tekst: Uniscribe snabdeva ScriptItemize, ScriptShape, ScriptPlace i ScriptLayout unutar jedne klase, dok HarfBuzz pokriva samo oblikovanje i postavljanje oko sopstvenih UAX #9 etapa komponente
Uniscribe pokriva sve četiri etape; prenosivi put mora sam snabdevi segmentaciju i vizuelni redosled

Oblikovač ne razrešuje fontove, i to je namerno

Uniscribe čita font binarnu datoteku iz GDI device konteksta. Nema prenosivog ekvivalenta tome, i izmišljanje jednog unutar jedinice oblikovanja značilo bi odlučivati, u ime svake aplikacije, da li fontovi dolaze iz fontconfig-a, iz CoreText-a, iz fascikle fontova aplikacije, ili iz baze podataka. Pa HarfBuzz backend prima razrešivača: callback koji mapira ime fonta u TrueType ili OpenType bajtove. Vraćanje False otkazuje zahtev oblikovanja istim načinom na koji ga nečitljiv GDI font otkazuje na Windows-u

uses
  FPdfTextShaping
{$IFDEF FPC}
  , FPdfTextShapingHb
{$ENDIF}
  ;

function TFontCatalogue.Resolve(const FontName: WideString;
  out FontData: TBytes): Boolean;
var
  Path: string;
begin
  // Vaša politika: fontconfig, CoreText, fascikla fontova aplikacije, baza podataka
  Result := FLookup.TryGetValue(LowerCase(FontName), Path);
  if Result then
    FontData := TFile.ReadAllBytes(Path);
end;

procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
  // Vlasništvo prelazi na jedinicu; pozovite jednom tokom pokretanja,
  // pre nego što bilo šta oblikuje tekst
  ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
  // Na Delphi-ju platformski podrazumevani (Uniscribe) stvara se na zahtev,
  // pa instalacija uopšte nije potrebna
  LogInfo('shaping backend: ' + ActiveTextShaperName);
end;

Držanje otkrivanja fontova van oblikovača ima drugu korist koja se pokazuje na serverima: isti proces može oblikovati sa ugrađenim skupom fontova koji nema nikakve veze sa onim što je instalirano na mašini, što je ono što želite kada izlaz mora biti bajt-reprodukući preko domaćina. Komponenta takođe izlaže pružaoca fontova host sistema za slučajeve gde ipak želite instalirane fontove, pokriveno u članku o pružaocu sistemskih fontova

Zapis rezultata je backend-neutralan, i klasteri su razlog

Oba backend-a pune isti TPdfShapedText: izvorni tekst, ime fonta, veličinu, bajtove fonta, niz nizova, ukupnu širinu, broj glifova i broj logičkih znakova. Svaki TPdfShapedRun nosi svoj deo u izvornom tekstu, svoju vizuelnu X poziciju, svoju širinu, svoj dvosmjerni nivo i zastavicu sdesna-ulevo, plus svoje glifove. Svaki TPdfShapedGlyph nosi identifikator glifa, napredovanje, X i Y ofsete, i klaster kojem pripada kao početak i dužinu u izvornom tekstu

Ta polja klastera ono su što čini zapis upotrebljivim umesto samo informativnim. Oblikovanje nije preslikavanje jedan-u-jedan: devanagarski slog postaje jedan glif iz četiri znaka, arapska ligatura spaja dva, i jedan znak može proizvesti nekoliko dijakritika. Bez delova klastera ne možete postaviti kursor, ispitati pogodak klikom, ni istaći izbor, jer ne možete reći kojim znakovima glif pripada. Sa njima, aritmetika je lokalna i isti kod radi za oba backend-a

Delovi klastera glifova u TPdfShapedText: jedan glif devanagarskog sloga iz četiri znaka, arapska ligatura iz dva, i baza plus dijakritika iz jednog znaka, svako mapiran nazad kroz ClusterStart i ClusterLength
Delovi klastera mapiraju svaki glif nazad u izvorne znakove pa kursori, provere pogodaka i izbori rade
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
      // Nizovi već stižu u vizuelnom redosledu sa popunjenim VisualX
      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;

Budžeti pripadaju zapisu opcija

TPdfTextShapingOptions nosi smer plus tri granice: najviše znakova, najviše glifova i najviše nizova, sa klasnom funkcijom Default koja puni razumne vrednosti. Granice nisu paranoja oko deformisanog ulaza; one su aritmetika. Oblikovanje se širi: font sa agresivnom kontekstualnom zamenom može emitovati više glifova nego ulaznih znakova, i pasus koji naizmenično menja pisma na svakih nekoliko znakova proizvodi niz po promeni. Dokument sklopljen da uveća oba pretvara skromni niz znakova u veliko alociranje, i servis koji oblikuje tekst iz nepoverljivih PDF-ova traži granicu koju je izabrao umesto granice koju mašina nameće

Postavljanje smera eksplicitno umesto ostavljanja na automatskom vredi raditi kad god to već znate. Automatski primenjuje pravila smera pasusa da pogodi iz prvog jakog znaka, što je ispravno za slobodan tekst i pogrešno za polje obrasca čiji je smer svojstvo polja umesto vrednosti koju je neko ukucao u njega

Vezivanje u vremenu izvršavanja, ne zavisnost izgradnje

HarfBuzz backend učitava biblioteku dinamički. To je odluka raspoređivanja sa stvarnim posledicama: jedna binarna datoteka radi na mašini sa HarfBuzz-om i na mašini bez njega, izveštavajući smanjenu mogućnost u drugom slučaju umesto da otkazuje pokretanje. Za biblioteku isporučenu drugim programerima to je jedini funkcionišući aranžman, jer ne možete zahtevati da svaki potrošač PDF komponente pribavi i uskladi verziju biblioteke oblikovanja koja mu možda ne treba

Odgovarajuće pravilo za pozivaoce je proveriti. ActiveTextShaper vraća nil kada platforma nema podrazumevani i nijedan nije konfigurisan, i ulazna tačka oblikovanja izveštava to kao nedostupnog oblikovača umesto kao neuspeh oblikovanja. To su različiti problemi i zaslužuju različite poruke: jedan je jaz raspoređivanja, drugi je problem fonta ili teksta

Instalirajte jednom, pre nego što bilo šta oblikuje

Instalacija zamenjuje i oslobađa prethodnog oblikovača, pa njeno ponavljanje bezbedno je ali beskorisno, i pozivanje dok druga nit oblikuje nikako nije bezbedno. Uradite to tokom pokretanja. Ako treba pasti nazad na platformski podrazumevani kasnije, prosledite nil, što je i način da poništite test dvojnika na kraju testa

Kada je backend jednom instaliran, merenje i prelamanje ponašaju se isto na obe platforme, pošto troše metrike nizova i glifova umesto da zovu platformu direktno; model prelamanja opisan je u članku o merenju teksta i prelamanju reči. Podržane platforme i toolchain-i za komponentu navedeni su na stranici proizvoda PDFium Delphi component