Techninis straipsnis

Keičiamasis teksto formavimas: Uniscribe ir HarfBuzz Delphi

Teksto formavimas PDFium komponentėje eina per vieną diegiamą objektą. ConfigureTextShaper įdiegia formuotoją, kuriuo eina kiekvienas formavimo įėjimo taškas, pakeisdamas ir atleisdamas, kas ten buvo; ActiveTextShaper grąžina įdiegtąjį ir pirmuoju naudojimu sukuria platformos numatytąjį; ActiveTextShaperName praneša, kuris posistemis gyvas; ClearTextShaper numeta įdiegtį ir leidžia numatytąjį sukurti iš naujo. Windows numatytasis yra TPdfUniscribeTextShaper. Free Pascal aplinkoje yra TPdfHarfBuzzTextShaper, susiejantis libharfbuzz vykdymo laiku, kad dingusi biblioteka būtų pranešama būsena, o ne įkėlimo nesėkmė

Keičiamosios teksto formavimo architektūra PDFium Delphi komponentėje: ConfigureTextShaper, ActiveTextShaper ir ClearTextShaper valdo vieną įdiegtą posistemį — Uniscribe Windows ir vykdymo laiku susiejamas HarfBuzz Free Pascal aplinkoje
Kiekvienas formavimo iškvietimas eina per vieną įdiegtą formuotojo objektą, su platformos numatytuoju kiekvienoje paskirtyje

Viena sąsaja, du posistemiai, dalijantys darbą visiškai skirtingai. To asimetrijos supratimas yra tai, kas neleidžia perkeliamajam keliui pagaminti teisingai suformuoto, bet neteisingai pozicionuoto teksto

Kodėl Windows posistemis yra viena klasė, o perkeliamasis — trys dalys?

Nes Uniscribe yra keturios API, apsimestos viena. ScriptItemize segmentuoja eilutę pagal rašto sistemą ir išsprendžia dvišalius lygius; ScriptShape atvaizduoja simbolius į glifus; ScriptPlace skaičiuoja priekio reikšmes ir poslinkius; ScriptLayout sudėlioja gautas atkarpas į vizualinę tvarką. Posistemis, pastatytas ant jos, todėl neturi ko pridėti — todėl Windows formuotojas yra viena klasė su vienu metodu

HarfBuzz dengia vidurinąsias dvi. Ji formuoja ir pozicionuoja atkarpą, kurios kryptį ir rašto sistemą iškviečiantysis jau nusprendė, ir ji neturi nuomonės, kaip pastraipa skyla į atkarpas ar kokia tvarka tos atkarpos pasirodo. Todėl perkeliamasis posistemis tiekia likusį: dvišalis algoritmas išsprendžia įdėjimo lygius, HarfBuzz Unicode funkcijos segmentuoja tekstą pagal rašto sistemą, o atkarpos išdėstomos vizualine tvarka, kurią duoda UAX #9 taisyklė L2. Dvišalioji pusė yra pakankamai didelė, kad būtų savas modulis — aprašytas UAX #9 įdėjimo lygių straipsnyje

PDF teksto formavimo konvejerio palyginimas: Uniscribe tiekia ScriptItemize, ScriptShape, ScriptPlace ir ScriptLayout vienoje klasėje, o HarfBuzz dengia tik formavimą ir pozicionavimą aplink komponentės pačios UAX #9 pakopas
Uniscribe dengia visas keturias pakopas; perkeliamasis kelias privalo pats tiekti segmentavimą ir vizualinę tvarką

Formuotojas neišsprendžia šriftų, ir tai sąmoninga

Uniscribe skaito šrifto dvejetainę iš GDI įrenginio konteksto. To nėra perkeliamas atitikmuo, ir tokio išradimas formavimo modulio viduje reikštų sprendimą kiekvienos programos vardu, iš kur šriftai ateina — iš fontconfig, iš CoreText, iš programos šriftų aplanko ar iš duomenų bazės. Todėl HarfBuzz posistemis ima skiriamąjį: atgalinį iškvietimą, šrifto vardą atvaizduojantį į TrueType arba OpenType baitus. False grąža priverčia formavimo užklausą nepavykti taip pat, kaip neskaitytinas GDI šriftas ją priverčia nepavykti Windows

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

function TFontCatalogue.Resolve(const FontName: WideString;
  out FontData: TBytes): Boolean;
var
  Path: string;
begin
  // Jūsų politika: fontconfig, CoreText, programos šriftų aplankas, duomenų bazė
  Result := FLookup.TryGetValue(LowerCase(FontName), Path);
  if Result then
    FontData := TFile.ReadAllBytes(Path);
end;

procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
  // Nuosavybė perduodama moduliui; kvieskite kartą paleidimo metu,
  // prieš bet ką formuojant tekstą
  ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
  // Delphi platformos numatytasis (Uniscribe) kuriamas pagal pareikalavimą,
  // todėl jokios įdiegties apskritai nereikia
  LogInfo('shaping backend: ' + ActiveTextShaperName);
end;

Šriftų atradimo laikymas už formuotojo ribų turi antrą naudą, pasirodantį serveriuose: tas pats procesas gali formuoti su įterptu šriftų rinkiniu, neturinčiu nieko bendro su tuo, kas įdiegta mašinoje, — to norite, kai išvestis turi būti baitais atkuriamas tarp mazgų. Komponentė taip pat atveria pagrindinės sistemos šriftų teikėją atvejams, kai vis dėlto norite įdiegtų šriftų — dengiamas sistemos šriftų teikėjo straipsnyje

Rezultato įrašas posistemiams neutralus, ir klasteriai yra priežastis

Abu posistemiai užpildo tą patį TPdfShapedText: šaltinio tekstą, šrifto vardą, dydį, šrifto baitus, atkarpų masyvą, bendrą plotį, glifų skaičių ir loginį simbolių skaičių. Kiekviena TPdfShapedRun neša savo iškarpą šaltinio tekste, savo vizualinę X padėtį, savo plotį, savo dvišalį lygį ir dešinės-kairės vėliavėlę, taip pat savo glifus. Kiekvienas TPdfShapedGlyph neša glifo identifikatorių, priekio reikšmę, X ir Y poslinkius ir klasterį, kuriam priklauso, kaip pradžią ir ilgį šaltinio tekste

Tie klasterio laukai yra tai, kas daro įrašą naudotinu, o ne tik informatyviu. Formavimas nėra vienas prie vieno atvaizdis: Devanagari skiemenys tampa vienu glifu iš keturių simbolių, arabų ligatūra sujungia du, o vienas simbolis gali pagaminti kelis diakritikus. Be klasterio iškarpų negalite padėti šydo, atlikti pataikymo testo ar paryškinti žymės, nes negalite pasakyti, kuriems simboliams glifas priklauso. Su jais aritmetika vietinė, ir tas pats kodas veikia abiem posistemiams

Glifų klasterio iškarpos TPdfShapedText: vienas Devanagari skiemens glifas iš keturių simbolių, arabų ligatūra iš dviejų ir pagrindas su diakritiku iš vieno simbolio, kiekvienas atvaizduotas atgal per ClusterStart ir ClusterLength
Klasterio iškarpos atvaizduoja kiekvieną glifą atgal į jo šaltinio simbolius, kad šydai, pataikymo testai ir žymės veiktų
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
      // Atkarpos jau atkeliauja vizualine tvarka su užpildytu 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;

Biudžetai priklauso parinkčių įrašui

TPdfTextShapingOptions neša kryptį ir tris ribas: didžiausią simbolių skaičių, didžiausią glifų skaičių ir didžiausią atkarpų skaičių, o Default klasės funkcija užpildo protingas reikšmes. Ribos nėra paranoja dėl neteisingos įvesties; jos yra aritmetika. Formavimas plečiasi: šriftas su agresyvia kontekstine pakeitimų sistema gali skleisti daugiau glifų nei įvesties simbolių, o pastraipa, kas kelis simbolius besikeičianti rašto sistemas, duoda atkarpą per kiekvieną perjungimą. Dokumentas, sudėliotas maksimizuoti abu, paverčia kuklią eilutę dideliu išskyrimu, o tarnyba, formuojanti tekstą iš nepatikimų PDF, reikalauja ribos, kurią ji parinko, o ne ribos, kurią nulemia mašina

Krypties nustatymas aiškiai, o ne palikimas automatiniam, vertas daroma kaskart, kai jau žinote. Automatinis taiko pastraipos krypties taisykles spėjimui iš pirmojo stipriojo simbolio, kas teisinga laisvam tekstui ir neteisinga formos laukui, kurio kryptis yra lauko savybė, o ne reikšmės, kurią kas nors į jį įrašė

Vykdymo laiko susiejimas, o ne statymo priklausomybė

HarfBuzz posistemis krauna biblioteką dinamiškai. Tai diegimo sprendimas su realiomis pasekmėmis: vienas dvejetainis failas veikia mašinoje su HarfBuzz ir mašinoje be jos, pranešdamas sumažintą gebą antruoju atveju, o ne nepavykdamas startuoti. Bibliotekai, išsiunčiamai kitiems kūrėjams, tai vienintelis veikiantis išdėstymas, nes negalite reikalauti, kad kiekvienas PDF komponentės vartotojas įsigytų ir versijomis suderintą formavimo biblioteką, kurios jam gali ir nereikėti

Atitinkama iškviečiųjų taisyklė — tikrinti. ActiveTextShaper grąžina nil, kai platforma neturi numatytojo ir joks nesukonfigūruotas, o formavimo įėjimo taškas praneša tai kaip neprieinamą formuotoją, o ne kaip formavimo nesėkmę. Tai skirtingos problemos ir nusipelno skirtingų pranešimų: viena yra diegimo spraga, kita — šrifto ar teksto problema

Įdiekite kartą, prieš bet ką formuojant

Įdiegtis pakeičia ir atleidžia ankstesnįjį formuotoją, todėl jos kvietimas pakartotinai saugus, bet beprasmis, o kvietimas, kol kita gija formuoja, apskritai nesaugus. Darykite tai paleidimo metu. Jeigu vėliau reikia nukristi į platformos numatytąjį, perduokite nil — taip pat atšaukiate testo dublį testo pabaigoje

Kai posistemis įdiegtas, matavimas ir žodžių perkėlimas elgiasi vienodai abiejose platformose, nes jie vartoja atkarpų ir glifų metrikas, o ne kviečia platformą tiesiogiai; perkėlimo modelis aprašytas teksto matavimo ir žodžių perkėlimo straipsnyje. Palaikomos komponentės platformos ir įrankių grandinės išvardytos PDFium Delphi component produkto puslapyje