Tvarování textu v komponentě PDFium prochází jedním instalovatelným objektem. ConfigureTextShaper instaluje shaper, skrze který směřuje každý vstupní bod tvarování, a nahradí a uvolní to, co tam bylo; ActiveTextShaper vrací nainstalovaný a vytvoří platformový výchozí při prvním použití; ActiveTextShaperName hlásí, který backend je živý; ClearTextShaper zruší instalaci a dovolí vytvořit výchozí znovu. Na Windows je výchozím TPdfUniscribeTextShaper. Pod Free Pascalem je tu TPdfHarfBuzzTextShaper, který váže libharfbuzz za běhu, takže chybějící knihovna je hlášený stav, nikoli selhání načtení
Jedno rozhraní, dva backendy, které si práci rozdělují úplně odlišně. Pochopení té asymetrie je to, co zastaví přenositelnou cestu od produkování textu správně tvarovaného a chybně pozicovaného
Proč je Windows backend jedna třída a přenositelný tři kusy?
Protože Uniscribe je čtyři API předstírající, že jsou jedno. ScriptItemize segmentuje řetězec podle psací soustavy a rozřešuje obousměrné úrovně; ScriptShape mapuje znaky na glyfy; ScriptPlace počítá rozestupy a offsety; ScriptLayout dává vzniklé runs do vizuálního pořadí. Backend postavený na něm proto nemá co přidat, což je důvod, proč je Windows shaper jediná třída s jedinou metodou
HarfBuzz pokrývá prostřední dvě. Tvaruje a pozicuje run, jehož směr a psací soustavu už volající rozhodl, a nemá žádný názor na to, jak se odstavec dělí na runs ani v jakém pořadí se ty runs objevují. Přenositelný backend proto dodává zbytek: obousměrný algoritmus rozřešuje úrovně zapouzdření, funkce Unicode HarfBuzu segmentují text podle psací soustavy a runs se rozkládají do vizuálního pořadí, které produkuje pravidlo L2 z UAX #9. Obousměrná polovina je podstatná natolik, že je vlastní unitou, popsanou v článku o úrovních zapouzdření UAX #9
Shaper nerozřešuje písma a to je záměrné
Uniscribe čte binární soubor písma z device contextu GDI. Přenositelný ekvivalent toho neexistuje a vymýšlet jeden uvnitř unity tvarování by znamenalo rozhodovat jménem každé aplikace, zda písma pocházejí z fontconfig, z CoreText, ze složky písem aplikace nebo z databáze. Backend HarfBuzu proto přebírá resolver: callback, který mapuje název písma na bajty TrueType nebo OpenType. Vrácení False selže žádost o tvarování stejným způsobem, jakým ji na Windows selže nečitelné písmo GDI
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// Vaše politika: fontconfig, CoreText, složka písem aplikace, databáze
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// Vlastnictví přechází na unitu; volejte jednou během startu,
// než cokoli tvaruje text
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// V Delphi se platformový výchozí (Uniscribe) vytváří na požádání,
// takže instalace není vůbec potřeba
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
Udržení hledání písem mimo shaper má druhý přínos, který se ukazuje na serverech: tentýž proces může tvarovat se sadou vložených písem, která nesouvisí s tím, co je nainstalováno na stroji, což je to, co chcete, když výstup musí být bajtově reprodukovatelný napříč hostiteli. Komponenta také vystavuje poskytovatele písem hostitelského systému pro případy, kdy nainstalovaná písma chcete, předmět článku o poskytovateli systémových písem
Záznam výsledku je backendově neutrální a důvodem jsou clustery
Oba backendy plní totéž TPdfShapedText: zdrojový text, název písma, velikost, bajty písma, pole runs, celkovou šířku, počet glyfů a počet logických znaků. Každý TPdfShapedRun nese svůj úsek ve zdrojovém textu, svou vizuální pozici X, šířku, svou obousměrnou úroveň a příznak zprava doleva, plus své glyfy. Každý TPdfShapedGlyph nese identifikátor glyfu, rozestup, offsety X a Y a cluster, do něhož patří, jako začátek a délku ve zdrojovém textu
Ty clusterové položky jsou tím, co dělá záznam použitelným, nikoli jen informativním. Tvarování není mapování jeden k jednomu: slabika dévanágarí se stane jedním glyfem ze čtyř znaků, arabská ligatura spojí dva a jediný znak může produkovat několik značek. Bez úseků clusterů nelze umístit kurzor, hit-testovat kliknutí ani zvýraznit výběr, protože nelze říci, ke kterým znakům glyf patří. S nimi je aritmetika lokální a týž kód funguje pro 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
// Runs už přicházejí ve vizuálním pořadí s vyplněný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 patří do záznamu voleb
TPdfTextShapingOptions nese směr plus tři stropy: maximální počet znaků, maximální počet glyfů a maximální počet runs, s třídní funkcí Default, která vyplní rozumné hodnoty. Stropy nejsou paranoia o deformovaném vstupu; jsou to aritmetika. Tvarování expanduje: písmo s agresivní kontextovou substitucí může vypustit víc glyfů než vstupních znaků a odstavec střídající psací soustavy po každých pár znacích produkuje run na každé přepnutí. Dokument sestavený k maximalizaci obojího mění skromný řetězec ve velkou alokaci a služba tvarující text z nedůvěryhodných PDF potřebuje limit, který si zvolila, namísto limitu, který vnucuje stroj
Nastavení směru explicitně namísto ponechání na automatickém stojí za to, kdykoli jej už znáte. Automatické aplikuje pravidla směru odstavce k dohadě z prvního silného znaku, což je správné pro volný text a špatné pro pole formuláře, jehož směr je vlastností pole, nikoli hodnoty, kterou do něj někdo napsal
Vázání za běhu, nikoli závislost sestavení
Backend HarfBuzu načítá knihovnu dynamicky. To je rozhodnutí o nasazení se skutečnými důsledky: jedna binárka běží na stroji s HarfBuzzem i na stroji bez něj a ve druhém případě hlásí sníženou schopnost namísto selhání při startu. Pro knihovnu dodávanou jiným vývojářům je to jediné funkční uspořádání, protože nemůžete vyžadovat, aby si každý konzument PDF komponenty opatřil a sladil na verzi tvarovací knihovnu, kterou možná nepotřebuje
Odpovídajícím pravidlem pro volající je kontrolovat. ActiveTextShaper vrací nil, když platforma nemá výchozí a nebyl nakonfigurován žádný, a vstupní bod tvarování to hlásí jako nedostupný shaper, nikoli jako selhání tvarování. To jsou různé problémy a zaslouží si různé zprávy: jedno je mezera v nasazení, druhé je problém písma nebo textu
Instalujte jednou, než cokoli tvaruje
Instalace nahradí a uvolní předchozí shaper, takže opakované volání je bezpečné, ale zbytečné, a volání, zatímco jiné vlákno tvaruje, není bezpečné vůbec. Udělejte to během startu. Potřebujete-li se později vrátit na platformový výchozí, předejte nil, což je také způsob, jak odvolat testovací dub na konci testu
Jakmile je backend nainstalován, chovají se měření a zalamování stejně na obou platformách, protože konzumují metriky runs a glyfů namísto přímého volání platformy; model zalamování popisuje článek o měření textu a zalamování slov. Podporované platformy a toolchainy komponenty jsou uvedeny na stránce produktu PDFium Delphi component