Textformning i PDFium-komponenten går genom ett installerbart objekt. ConfigureTextShaper installerar formaren som varje formningsingångspunkt leder genom, ersättande och frigörande vadhelst som fanns där; ActiveTextShaper returnerar den installerade och skapar plattformsstandarden vid första användningen; ActiveTextShaperName rapporterar vilken backend som är aktiv; ClearTextShaper tappar installationen och låter standarden skapas igen. På Windows är standarden TPdfUniscribeTextShaper. Under Free Pascal finns TPdfHarfBuzzTextShaper, som binder libharfbuzz vid körtid så att ett saknat bibliotek är ett rapporterat tillstånd i stället för ett laddfel
Ett gränssnitt, två backends som delar arbetet helt olika. Att förstå den asymmetrin är det som hindrar det portabla spåret från att producera text som är formad korrekt och placerad fel
Varför är Windows-backend en klass och den portabla tre delar?
För Uniscribe är fyra API:er som låtsas vara ett. ScriptItemize segmenterar en sträng per skript och löser tvåriktade nivåer; ScriptShape mappar tecken till glyfer; ScriptPlace beräknar framåtlöpningar och offset; ScriptLayout lägger de resulterande körningarna i visuell ordning. En backend byggd på den har därför ingenting kvar att lägga till, vilket är varför Windows-formaren är en enda klass med en enda metod
HarfBuzz täcker de mellersta två. Den formar och placerar en körning vars riktning och skript anroparen redan har beslutat, och den har ingen åsikt om hur ett stycke delas i körningar eller i vilken ordning de körningarna uppträder. Så den portabla backenden tillhandahåller resten: den tvåriktade algoritmen löser inbäddningsnivåer, HarfBuzz Unicode-funktioner segmenterar texten per skript och körningarna läggs ut i den visuella ordning som UAX #9-regel L2 producerar. Den tvåriktade hälften är tillräckligt omfattande för att vara sin egen enhet, beskriven i artikeln om UAX #9-inbäddningsnivåer
Formaren löser inte teckensnitt, och det är med flit
Uniscribe läser teckensnittsbinären ur en GDI device context. Det finns ingen portabel motsvarighet till det, och att hitta på en inuti en formningsenhet skulle betyda att besluta, på alla applikationers vägnar, huruvida teckensnitt kommer från fontconfig, från CoreText, från en applikationsfontmapp eller från en databas. Så HarfBuzz-backenden tar en resolver: ett callback som mappar ett teckensnittsnamn till TrueType- eller OpenType-bytena. Att returnera False fäller formningsbegäran på samma sätt som ett oläsbart GDI-teckensnitt fäller den på Windows
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// Din policy: fontconfig, CoreText, en appfontmapp, en databas
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// Ägandet övergår till enheten; anropa en gång vid uppstarten,
// innan något formar text
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// På Delphi skapas plattformsstandarden (Uniscribe) på begäran,
// så ingen installation behövs alls
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
Att hålla teckensnittsfinning utanför formaren har en andra fördel som visar sig i servrar: samma process kan forma med en inbäddad fontuppsättning som inte har något att göra med vad som är installerat på maskinen, vilket är vad du vill ha när utmatningen måste vara byte-reproducerbar mellan värdar. Komponenten exponerar också en värdsystemfontleverantör för fallen där du faktiskt vill ha installerade teckensnitt, tas upp i artikeln om systemfontleverantören
Resultatposten är backend-neutral, och kluster är orsaken
Båda backends fyller samma TPdfShapedText: källtexten, teckensnittsnamnet, storleken, teckensnittsbytena, en array av körningar, den totala bredden, glyfantalet och det logiska teckenantalet. Varje TPdfShapedRun bär sitt stycke i källtexten, sin visuella X-position, sin bredd, sin tvåriktade nivå och en höger-till-vänster-flagga, plus sina glyfer. Varje TPdfShapedGlyph bär en glyfidentifierare, en framåtlöpning, X- och Y-offset och klustret den tillhör som en start och en längd i källtexten
De klusterfälten är det som gör posten användbar snarare än blott informativ. Formning är inte en en-till-en-mappning: en devanagari stavelse blir en glyf ur fyra tecken, en arabisk ligatur slår ihop två, och ett enskilt tecken kan producera flera diakritiska märken. Utan klusterstycken kan du inte placera en textmarkör, träff testa ett klick eller markera ett urval, eftersom du inte kan säga vilka tecken en glyf tillhör. Med dem är aritmetiken lokal och samma kod fungerar för båda backends
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
// Körningar anländer redan i visuell ordning med VisualX ifyllt
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;
Budgetar hör hemma i alternativposten
TPdfTextShapingOptions bär en riktning plus tre tak: maximalt antal tecken, maximalt antal glyfer och maximalt antal körningar, med en klassfunktion Default som fyller förnuftiga värden. Taken är inte paranoia om felformerad indata; de är aritmetik. Formning expanderar: ett teckensnitt med aggressiv kontextuell substitution kan avge fler glyfer än indatatecken, och ett stycke som växlar skript vart några tecken producerar en körning per växling. Ett dokument monterat för att maximera båda gör en blygsam sträng till en stor allokering, och en tjänst som formar text ur obetrodda PDF:er behöver en gräns den valde i stället för en gräns maskinen påtvingar
Att ställa in riktningen uttryckligen i stället för att lämna den på automatisk är värt att göra närhelst du redan vet den. Automatisk tillämpar styckriktningsreglerna för att gissa från första starka tecknet, vilket är rätt för fri text och fel för ett formulärfält vars riktning är en egenskap hos fältet snarare än hos värdet någon skrev in i det
Bindning vid körtid, inte ett byggberoende
HarfBuzz-backenden laddar biblioteket dynamiskt. Det är ett distributionsbeslut med verkliga konsekvenser: en binärfil körs på en maskin med HarfBuzz och på en maskin utan den, med rapporterad reducerad förmåga i det andra fallet i stället för att misslyckas starta. För ett bibliotek levererat till andra utvecklare är det det enda fungerande upplägget, eftersom du inte kan kräva att varje konsument av en PDF-komponent skaffar och versionsmatchar ett formningsbibliotek de kanske inte behöver
Motsvarande regel för anropare är att kontrollera. ActiveTextShaper returnerar nil när plattformen inte har någon standard och ingen konfigurerades, och formningsingångspunkten rapporterar det som en otillgänglig formare i stället för som ett formningsfel. De är olika problem och förtjänar olika meddelanden: den ena är ett distributionsgap, den andra ett teckensnitts- eller textproblem
Installera en gång, innan något formar
Installationen ersätter och frigör den tidigare formaren, så att anropa den upprepade gånger är säkert men poänglöst, och att anropa den medan en annan tråd formar är inte säkert alls. Gör det under uppstarten. Om du behöver falla tillbaka till plattformsstandarden senare, skicka nil, vilket också är hur du ogör ett testdubbel i slutet av ett test
När en backend väl är installerad beter sig mätning och radbrytning likadant på båda plattformarna, eftersom de konsumerar körnings- och glyfmåtten i stället för att anropa plattformen direkt; radbrytningsmodellen beskrivs i artikeln om textmätning och ordlindning. Stödda plattformar och verktygskedjor för komponenten listas på produktsidan för PDFium Delphi component