Tehnični članak

Oblikovanje besedila: Uniscribe in HarfBuzz v Delphiju

Oblikovanje besedila v komponenti PDFium gre skozi en namestitveni objekt. ConfigureTextShaper namesti oblikovalca, skozi katerega se usmerja vsak vstopni klic oblikovanja, zamenjajoč in sproščujoč karkoli je bilo tam; ActiveTextShaper vrne nameščenega in ustvari privzeto platformo ob prvi uporabi; ActiveTextShaperName poroča, katero zaledje je živo; ClearTextShaper spusti namestitev in pusti privzeto biti znova ustvarjeno. Na Windows je privzeto TPdfUniscribeTextShaper. Pod Free Pascalom obstaja TPdfHarfBuzzTextShaper, ki veže libharfbuzz ob zagonu, tako da je manjkajoča knjižnica poročano stanje in ne odpoved nalaganja

Arhitektura priključljivega oblikovanja besedila v komponenti PDFium Delphi: ConfigureTextShaper, ActiveTextShaper in ClearTextShaper upravljajo eno nameščeno zaledje, Uniscribe na Windows in vezavo HarfBuzz ob zagonu pod Free Pascalom
Vsak klic oblikovanja se usmerja skozi en sam nameščen objekt oblikovalca, s privzeto platformo na vsakem cilju

En vmesnik, dve zaledji, ki delita delo popolnoma drugače. Razumevanje te asimetrije je tisto, kar ustavi prenosljivo pot pred proizvodnjo besedila, ki je oblikovano pravilno in pozicionirano napačno

Zakaj je zaledje Windows en razred in prenosljivo trije kosi?

Ker je Uniscribe štiri API-ji, ki se pretvarjajo, da so enega. ScriptItemize razdeli niz po skripti in razreši dvosmjerne ravni; ScriptShape preslika znake v glife; ScriptPlace izračuna predkorake in odmike; ScriptLayout da nastale teke v vizualni vrstni red. Zaledje, zgrajeno nanj, torej nima ničesar več za dodati, kar je razlog, zakaj je oblikovalec Windows en razred z eno metodo

HarfBuzz pokriva srednji dve. Oblikuje in umešča tek, katerega smer in skripto je klicatelj že odločil, in nima mnenja o tem, kako se odstavek razdeli v teke ali v katerem vrstnem redu se ti teki pojavijo. Torej prenosljivo zaledje preskrbi ostalo: dvosmjerni algoritem razreši ravni vgradnje, funkcije Unicode HarfBuzz razdelijo besedilo po skripti in teki so razporejeni v vizualnem vrstnem redu, ki ga proizvede pravilo L2 UAX #9. Dvosmjerna polovica je dovolj obsežna, da je lastna enota, opisana v članku o ravnih vgradnje UAX #9

Primerjava cevovoda oblikovanja za besedilo PDF: Uniscribe preskrbi ScriptItemize, ScriptShape, ScriptPlace in ScriptLayout znotraj enega razreda, medtem ko HarfBuzz pokriva samo oblikovanje in umestitev okoli lastnih stopenj UAX #9 komponente
Uniscribe pokriva vse štiri stopnje; prenosljiva pot mora sama preskrbeti razdeljevanje po predmetih in vizualni vrstni red

Oblikovalec ne razrešuje pisav in to je namerno

Uniscribe bere binarno pisavo iz konteksta naprave GDI. Ni prenosljivega ustrežnika tega in izumljanje enega znotraj oblikovalne enote bi pomenilo odločanje, v imenu vsake aplikacije, ali pisave pridejo iz fontconfig, iz CoreText, iz mape pisav aplikacije ali iz podatkovne baze. Torej zaledje HarfBuzz vzame razreševalnik: povratni klic, ki preslika ime pisave v bajte TrueType ali OpenType. Vračanje False spodleti zahtevo oblikovanja na enak način, kot jo spodleti neberljiva pisava GDI na Windows

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 pisav aplikacije,
  // podatkovna baza
  Result := FLookup.TryGetValue(LowerCase(FontName), Path);
  if Result then
    FontData := TFile.ReadAllBytes(Path);
end;

procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
  // Lastnina gre k enoti; pokličite enkrat med zagonom,
  // preden karkoli oblikuje besedilo
  ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
  // Na Delphiju je privzeta platforma (Uniscribe) ustvarjena na zahtevo,
  // tako da ni potrebna nobena namestitev sploh
  LogInfo('shaping backend: ' + ActiveTextShaperName);
end;

Ohranjanje odkrivanja pisav zunaj oblikovalca ima drugo korist, ki se pokaže na strežnikih: isti proces lahko oblikuje z naborom vgrajenih pisav, ki nima ničesar s tem, kar je nameščeno na stroju, kar je tisto, kar želite, ko mora biti izhod bajtno reproducirljiv čez gostitelje. Komponenta prav tako izpostavi ponudnika pisav gostiteljskega sistema za primere, kjer res želite nameščene pisave, pokrito v članku o ponudniku sistemskih pisav

Zapis rezultata je zaledje-nevtralen in klastri so razlog

Obe zaledji izpolnita isti TPdfShapedText: izvorno besedilo, ime pisave, velikost, bajte pisave, matriko tekov, skupno širino, število glifov in logično število znakov. Vsak TPdfShapedRun nosi svoj razpon v izvornem besedilu, svoj vizualni položaj X, svojo širino, svojo dvosmjerno raven in zastavico z desne na levo, plus svoje glife. Vsak TPdfShapedGlyph nosi identifikator glifa, predkorak, odmika X in Y in klaster, ki mu pripada, kot začetek in dolžino v izvornem besedilu

Tista polja klastrov so tisto, kar naredi zapis uporaben in ne zgolj informativen. Oblikovanje ni preslikava ena-na-ena: zlog Devanagari postane en glif iz štirih znakov, arabska ligatura združi dva in en sam znak lahko proizvede več oznak. Brez razponov klastrov ne morete umeriti strešice, izvesti preizkusa zadetka klik ali poudariti izbire, ker ne morete povedati, katerim znakom glif pripada. Z njimi je aritmetika lokalna in ista koda deluje za obe zaledji

Razponi klastrov glifov v TPdfShapedText: en glif zloga Devanagari iz štirih znakov, arabska ligatura iz dveh in osnova plus oznaka iz enega znaka, vsak preslikan nazaj skozi ClusterStart in ClusterLength
Razponi klastrov preslikajo vsak glif nazaj na njegove izvorne znake, tako da delujejo strešice, preizkusi zadetkov in izbire
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
      // Teki že prispejo v vizualnem vrstnem redu z izpolnjenim 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 pripadajo zapisu možnosti

TPdfTextShapingOptions nosi smer plus tri omejitve: največ znakov, največ glifov in največ tekov, z razredno funkcijo Default, ki izpolni smiselne vrednosti. Omejitve niso paranoja o deformiranem vhodu; so aritmetika. Oblikovanje se razširi: pisava z agresivno kontekstualno substitucijo lahko izda več glifov kot vhodnih znakov in odstavek, ki izmenjuje skripte vsakih nekaj znakov, proizvede tek na preklop. Dokument, sestavljen, da maksimirata oboje, spremeni skromen niz v veliko dodelitev, in storitev, ki oblikuje besedilo iz nezaupanih PDF-jev, potrebuje omejitev, ki jo je izbrala, in ne omejitve, ki jo vsili stroj

Nastavitev smeri izrecno namesto puščanje na samodejni je vredna početja, kadar koli jo že veste. Samodejni aplicira pravila smeri odstavka, da ugane iz prvega močnega znaka, kar je prav za prosto besedilo in narobe za polje obrazca, katerega smer je lastnost polja in ne vrednosti, ki jo je nekdo natipal vanj

Vezava ob zagonu in ne gradbena odvisnost

Zaledje HarfBuzz naloži knjižnico dinamično. To je odločitev namestitve z resničnimi posledicami: ena binarna datoteka teče na stroju s HarfBuzz in na stroju brez njega, poročajoč zmanjšano zmožnost v drugem primeru namesto spodletel zagon. Za knjižnico, poslano drugim razvijalcem, je to edina delujoča ureditev, ker ne morete zahtevati, da vsak potrošnik PDF komponente pridobi in ujema z različico oblikovalno knjižnico, ki je morda ne potrebuje

Ustrezno pravilo za klicatelje je preveriti. ActiveTextShaper vrne nil, ko platforma nima privzetega in ni bilo nastavljenega nobenega, vstopna točka oblikovanja pa to poroča kot nerazpoložljivega oblikovalca in ne kot odpoved oblikovanja. To so različni problemi in si zaslužijo različna sporočila: eno je vrzel namestitve, drugo je težava pisave ali besedila

Namestite enkrat, preden karkoli oblikuje

Namestitev zamenja in sprosti prejšnjega oblikovalca, tako da je klicanje njega večkrat varno ampak brez točke, in klicanje njega, medtem ko druga nit oblikuje, ni varno sploh. Naredite to med zagonom. Če morate pasti nazaj na privzeto platformo kasneje, posredujte nil, kar je prav tako način, kako razveljavite dvojnika testa na koncu testa

Ko je zaledje nameščeno, se merjenje in ovijanje obnašata enako na obeh platformah, ker porabita metrike tekov in glifov namesto klicanja platforme neposredno; model ovijanja je opisan v članku o merjenju besedila in ovijanju besed. Podprte platforme in orodjarne za komponento so navedene na strani produkta PDFium Delphi component