Tvarovanie textu v komponente PDFium prechádza cez jeden inštalovateľný objekt. ConfigureTextShaper inštaluje shaper, ktorým prechádza každý vstupný bod tvarovania, pričom nahradí a uvoľní to, čo tam bolo; ActiveTextShaper vracia inštalovaný a pri prvom použití vytvorí platformovú predvolenú; ActiveTextShaperName hlási, ktorý backend je aktívny; ClearTextShaper zloží inštaláciu a dovolí, aby sa predvolená vytvorila znovu. Na Windows je predvolená TPdfUniscribeTextShaper. Pod Free Pascalom existuje TPdfHarfBuzzTextShaper, ktorý viaže libharfbuzz za behu, takže chýbajúca knižnica je hlásený stav, nie zlyhanie načítania
Jedno rozhranie, dva backendy, ktoré delia prácu úplne odlišne. Pochopenie tej asymetrie je to, čo bráni prenosnej ceste produkovať text tvarovaný správne a pozicionovaný zle
Prečo je backend Windows jedna trieda a prenosný tri kusy?
Pretože Uniscribe sú štyri API predstierajúce jedno. ScriptItemize segmentuje reťazec podľa scriptu a rozrieši bidirekcionálne úrovne; ScriptShape mapuje znaky na glyfy; ScriptPlace počíta posuny a ofsety; ScriptLayout dáva výsledné behy do vizuálneho poradia. Backend na nich postavený preto nemá čo pridať, čo je dôvod, prečo je Windows shaper jediná trieda s jedinou metódou
HarfBuzz pokrýva stredné dve. Tvaruje a umiestňuje beh, ktorého smer a script už volajúci rozhodol, a nemá žiadny názor na to, ako sa odsek delí na behy ani v akom poradí tie behy vystupujú. Prenosný backend preto dodáva zvyšok: bidirekcionálny algoritmus rozrieši úrovne vloženia, Unicode funkcie HarfBuzz segmentujú text podľa scriptu a behy sa rozložia do vizuálneho poradia, ktoré produkuje pravidlo L2 z UAX #9. Bidirekcionálna polovica je dosť rozsiahla na vlastnú jednotku, popísanú v článku o úrovniach vloženia UAX #9
Shaper nerozriešava fonty a to je zámerné
Uniscribe číta binárku fontu z device context GDI. Nie je k tomu prenosný ekvivalent a vymyslieť ho vnútri jednotky tvarovania by znamenalo rozhodnúť v mene každej aplikácie, či fonty pochádzajú z fontconfig, z CoreText, z priečinka fontov aplikácie alebo z databázy. HarfBuzz backend preto berie resolver: callback, ktorý mapuje meno fontu na bajty TrueType či OpenType. Vrátenie False zlyhá požiadavku na tvarovanie rovnakým spôsobom, akým ju na Windows zlyhá nečitateľný 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, priečinok fontov aplikácie, databáza
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// Vlastníctvo prechádza na jednotku; volajte raz pri štarte,
// skôr než čokoľvek tvaruje text
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// Na Delphi sa platformová predvolená (Uniscribe) vytvára na požiadanie,
// takže inštalácia nie je vôbec potrebná
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
Udržiavanie objavovania fontov mimo shaperu má druhý úžitok, ktorý sa ukáže na serveroch: ten istý proces môže tvarovať so sadou vložených fontov, ktorá nemá nič s tým, čo je nainštalované na stroji, čo je to, čo chcete, keď výstup musí byť reprodukovateľný po bajtoch naprieč hostmi. Komponent tiež vystavuje providera fontov hostiteľského systému pre prípady, keď chcete nainštalované fonty, pokryté v článku o providera systémových fontov
Záznam výsledku je backendovo neutrálny a klastre sú dôvodom
Oba backendy plnia ten istý TPdfShapedText: zdrojový text, meno fontu, veľkosť, bajty fontu, pole behov, celkovú šírku, počet glyfov a počet logických znakov. Každý TPdfShapedRun nesie svoj úsek v zdrojovom texte, svoju vizuálnu pozíciu X, svoju šírku, svoju bidirekcionálnu úroveň a príznak sprava doľava plus svoje glyfy. Každý TPdfShapedGlyph nesie identifikátor glyfu, posun, ofsety X a Y a klaster, do ktorého patrí, ako začiatok a dĺžku v zdrojovom texte
Tie polia klastrov sú to, čo robí záznam použiteľným, nie len informatívnym. Tvarovanie nie je vzájomne jednoznačné mapovanie: dévanágaríjska slabika sa stáva jedným glyfom zo štyroch znakov, arabská ligatúra spája dva a jediný znak môže produkovať niekoľko znamienok. Bez úsekov klastrov nemôžete umiestniť caret, spraviť hit-test kliknutia ani zvýrazniť výber, pretože nemôžete povedať, ktorým znakom glyf patrí. S nimi je aritmetika lokálna a ten istý kód funguje pre oba backendy
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
// Behy prichádzajú už vo vizuálnom poradí s vyplneným 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;
Rozpočty patria do záznamu volieb
TPdfTextShapingOptions nesie smer plus tri stropy: maximálne znaky, maximálne glyfy a maximálne behy, s triedovou funkciou Default, ktorá vyplní rozumné hodnoty. Stropy nie sú paranoja z deformovaného vstupu; sú to aritmetika. Tvarovanie sa rozširuje: font s agresívnou kontextovou substitúciou môže emitovať viac glyfov než vstupných znakov a odsek, ktorý strieda scripty každých pár znakov, produkuje beh na každú zmenu. Dokument zložený na maximalizovanie oboch mení skromný reťazec na veľkú alokáciu a služba tvarujúca text z nedôveryhodných PDF potrebuje limit, ktorý si zvolila, nie limit, ktorý ukladá stroj
Nastavenie smeru explicitne namiesto ponechania na automatickom stojí za to vždy, keď ho už viete. Automatické aplikuje pravidlá smeru odseku na hádanie z prvého silného znaku, čo je správne pre voľný text a zlé pre pole formulára, ktorého smer je vlastnosťou poľa, nie hodnoty, ktorú do neho niekto vpísal
Viazanie za behu, nie zostavovacia závislosť
HarfBuzz backend načítava knižnicu dynamicky. Toto je rozhodnutie o nasadení s reálnymi dôsledkami: jeden binárny súbor beží na stroji s HarfBuzz aj na stroji bez neho a v druhom prípade hlási znížené schopnosti namiesto zlyhania pri štarte. Pre knižnicu dodávanú iným vývojárom je to jediné fungujúce usporiadanie, pretože nemôžete vyžadovať, aby si každý konzument PDF komponentu zaobstaral a zverzoval tvarovaciu knižnicu, ktorú možno nepotrebuje
Zodpovedajúce pravidlo pre volajúcich je kontrolovať. ActiveTextShaper vracia nil, keď platforma nemá predvolenú a žiadna nebola nakonfigurovaná a vstupný bod tvarovania to hlási ako nedostupný shaper, nie ako zlyhanie tvarovania. Sú to odlišné problémy a zaslúžia si odlišné správy: jedna je medzera v nasadení, druhá je problém fontu či textu
Inštalujte raz, skôr než čokoľvek tvaruje
Inštalácia nahradí a uvoľní predchádzajúci shaper, takže jej opakované volanie je bezpečné, ale zbytočné a jej volanie, kým iné vlákno tvaruje, nie je vôbec bezpečné. Urobte to pri štarte. Ak sa potrebujete vrátiť na platformovú predvolenú, odovzdajte nil, čo je tiež spôsob, ako na konci testu zrušíte testovací dvojitý objekt
Keď je raz backend inštalovaný, meranie aj zalomenie sa správajú rovnako na oboch platformách, keďže konzumujú metriky behov a glyfov namiesto priameho volania platformy; model zalomenia popisuje článok o meraní textu a zalomení slov. Podporované platformy a toolchainy komponentu sú uvedené na produktovej stránke PDFium Delphi component