Tehnički članak

Priključivo oblikovanje: Uniscribe i HarfBuzz u Delphiju

Oblikovanje teksta u PDFium komponenti prolazi kroz jedan ugradivi objekt. ConfigureTextShaper ugrađuje oblikovač kroz koji se usmjerava svaka ulazna točka oblikovanja, zamjenjujući i oslobađajući ono što je tamo bilo; ActiveTextShaper vraća ugrađeni i stvara platformski zadanog pri prvoj uporabi; ActiveTextShaperName javlja koja je pozadina živa; ClearTextShaper ispušta ugradnju i dopušta da se zadan ponovno stvori. Na Windowsu zadan je TPdfUniscribeTextShaper. Pod Free Pascalom postoji TPdfHarfBuzzTextShaper, koji veže libharfbuzz u vremenu izvođenja tako da je nedostajuća knjižnica javljeno stanje umjesto neuspjeha učitavanja

Arhitektura priključivog oblikovanja teksta u PDFium Delphi komponenti: ConfigureTextShaper, ActiveTextShaper i ClearTextShaper upravljaju jednom ugrađenom pozadinom, Uniscribe na Windowsu i HarfBuzz vezan u vremenu izvođenja pod Free Pascalom
Svaki poziv oblikovanja usmjerava se kroz jedinstveni ugrađeni objekt oblikovača, s platformskim zadanim na svakom cilju

Jedno sučelje, dvije pozadine koje dijele posao potpuno drugačije. Razumijevanje te asimetrije ono je što sprječava prijenosni put da proizvede tekst koji je oblikovan ispravno a pozicioniran pogrešno

Zašto je Windows pozadina jedna klasa a prijenosna tri dijela?

Zato što je Uniscribe četiri API-ja koji se pretvaraju da su jedan. ScriptItemize segmentira niz po pismu i razrješuje dvosmjerne razine; ScriptShape preslikava znakove u glifove; ScriptPlace računa pomake i odstupanja; ScriptLayout stavlja nastale nizove u vizualni red. Pozadina izgrađena na njemu dakle nema ništa za dodati, pa je Windows oblikovač jedna klasa s jednom metodom

HarfBuzz pokriva srednja dva. Oblikuje i smješta niz čiji su smjer i pismo već odlučeni od pozivatelja, i nema mišljenja o tome kako se odlomak dijeli u nizove ni u kojem redu se ti nizovi pojavljuju. Prijenosna pozadina dakle opskrbljuje ostalo: dvosmjerni algoritam razrješuje razine ugnježđivanja, HarfBuzz Unicode funkcije segmentiraju tekst po pismu, a nizovi se raščlanjuju u vizualni red koji proizvodi pravilo L2 UAX #9. Dvosmjerna polovica dovoljno je značajna da bude vlastita jedinica, opisana u članku o razinama ugnježđivanja UAX #9

Usporedba cjevovoda oblikovanja za PDF tekst: Uniscribe opskrbljuje ScriptItemize, ScriptShape, ScriptPlace i ScriptLayout unutar jedne klase, dok HarfBuzz pokriva samo oblikovanje i smještanje oko vlastitih stupnjeva UAX #9 komponente
Uniscribe pokriva sve četiri stupnja; prijenosni put mora sam opskrbiti razvrstavanje u stavke i vizualni red

Oblikovač ne razrješuje fontove, i to je namjerno

Uniscribe čita binarnu datoteku fonta iz GDI konteksta uređaja. Nema prijenosnog ekvivalenta tome, i izmišljanje jednog unutar jedinice oblikovanja značilo bi odlučivati u ime svake aplikacije dolaze li fontovi iz fontconfiga, iz CoreTexta, iz mape fontova aplikacije ili iz baze podataka. HarfBuzz pozadina zato uzima razrješivač: povratni poziv koji preslikava ime fonta u TrueType ili OpenType bajtove. Vraćanje False obara zahtjev oblikovanja na isti način na koji ga na Windowsu obara nečitljiv GDI font

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, mapa 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 tijekom pokretanja,
  // prije nego bilo što oblikuje tekst
  ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
  // Na Delphiju platformski zadan (Uniscribe) stvara se na zahtjev,
  // pa uopće nije potrebna ugradnja
  LogInfo('shaping backend: ' + ActiveTextShaperName);
end;

Zadržavanje otkrivanja fontova izvan oblikovača ima drugu korist koja se pokazuje na poslužiteljima: isti proces može oblikovati s ugrađenim skupom fontova koji nema nikakve veze s onim što je instalirano na stroju, a to je ono što želite kad izlaz mora biti bajtovno ponovljiv između računala. Komponenta također izlaže davatelja fontova hostsustava za slučajeve u kojima ipak želite instalirane fontove, pokriveno u članku o davatelju sistemskih fontova

Zapis rezultata neutralan je prema pozadini, a skupine su razlog

Obje pozadine pune isti TPdfShapedText: izvorni tekst, ime fonta, veličinu, bajtove fonta, polje nizova, ukupnu širinu, broj glifova i logički broj znakova. Svaki TPdfShapedRun nosi svoj raspon u izvornom tekstu, svoj vizualni položaj X, svoju širinu, svoju dvosmjernu razinu i zastavicu s desna nalijevo, plus svoje glifove. Svaki TPdfShapedGlyph nosi identifikator glifa, pomak, odstupanja X i Y, i skupinu kojoj pripada kao početak i duljinu u izvornom tekstu

Ta polja skupine ono su što zapis čini upotrebljivim umjesto samo informativnim. Oblikovanje nije preslikavanje jedan-na-jedan: slog devanagari postaje jedan glif od četiri znaka, arapska ligatura spaja dva, a jedan znak može proizvesti više dijakritika. Bez raspona skupina ne možete smjestiti umetak, testirati pogodak klika ni označiti odabir, jer ne možete reći kojim znakovima glif pripada. S njima je aritmetika lokalna i isti kôd radi za obje pozadine

Rasponi skupina glifova u TPdfShapedText: jedan glif sloga devanagari od četiri znaka, arapska ligatura od dva, i baza plus dijakritika od jednog znaka, svaki preslikan natrag kroz ClusterStart i ClusterLength
Rasponi skupina preslikavaju svaki glif natrag na njegove izvorne znakove tako da umetci, testovi pogodaka i odabiri 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 vizualnom redu s 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;

Proračuni pripadaju zapisu opcija

TPdfTextShapingOptions nosi smjer plus tri granice: najviše znakova, najviše glifova i najviše nizova, s klasnom funkcijom Default koja puni razumne vrijednosti. Granice nisu paranoja zbog neispravnog unosa; to je aritmetika. Oblikovanje se širi: font s agresivnom kontekstualnom zamjenom može emitirati više glifova nego ulaznih znakova, i odlomak koji mijenja pisma svakih par znakova proizvodi niz po promjeni. Dokument sastavljen da oba maksimizira pretvara skromni niz u veliku dodjelu, i servis koji oblikuje tekst iz nepouzdanih PDF-ova treba granicu koju je odabrao umjesto granice koju nameće stroj

Postavljanje smjera izričito umjesto ostavljanja na automatsko vrijedi raditi kad god ga već znate. Automatski primjenjuje pravila smjera odlomka da bi pogađao iz prvog snažnog znaka, što je ispravno za slobodan tekst i pogrešno za polje obrasca čiji je smjer svojstvo polja, a ne vrijednosti koju je netko upisao u njega

Vezanje u vremenu izvođenja, ne ovisnost o izgradnji

HarfBuzz pozadina učitava knjižnicu dinamički. To je odluka o raspoređivanju sa stvarnim posljedicama: jedna binarna datoteka radi na stroju s HarfBuzzom i na stroju bez njega, javljajući smanjenu sposobnost u drugom slučaju umjesto neuspjeha pokretanja. Za knjižnicu isporučenu drugim razvojnim programerima to je jedini radni aranžman, jer ne možete zahtijevati da svaki potrošač PDF komponente pribavi i uskladi verziju knjižnice oblikovanja koju možda ne treba

Odgovarajuće pravilo za pozivatelje jest provjeriti. ActiveTextShaper vraća nil kada platforma nema zadanog i nijedan nije konfiguriran, a ulazna točka oblikovanja javlja to kao nedostupan oblikovač umjesto kao neuspjeh oblikovanja. To su različiti problemi i zaslužuju različite poruke: jedan je jaz u raspoređivanju, drugi problem fonta ili teksta

Ugradite jednom, prije nego bilo što oblikuje

Ugradnja zamjenjuje i oslobađa prethodni oblikovač, pa je ponovljeno pozivanje sigurno ali besmisleno, a pozivanje dok druga nit oblikuje uopće nije sigurno. Učinite to tijekom pokretanja. Ako trebate kasnije pasti na platformski zadanog, predajte nil, što je također način na koji poništavate dvostruko za test na kraju testa

Nakon što je pozadina ugrađena, mjerenje i prelamanje ponašaju se isto na obje platforme, jer troše metrike nizova i glifova umjesto da pozivaju platformu izravno; model prelamanja opisan je u članku o mjerenju teksta i prelamanju riječi. Podržane platforme i alatni lanci za komponentu navedeni su na stranici proizvoda PDFium Delphi component