Tekstshaping in de PDFium-component verloopt door één instelbaar object. ConfigureTextShaper installeert de shaper waardoor elk shaping-toegangspunt routeert, en vervangt en geeft vrij wat er stond; ActiveTextShaper geeft de geïnstalleerde terug en maakt de platformstandaard bij eerste gebruik aan; ActiveTextShaperName meldt welke backend actief is; ClearTextShaper laat de installatie vallen en laat de standaard opnieuw worden aangemaakt. Onder Windows is de standaard TPdfUniscribeTextShaper. Onder Free Pascal bestaat TPdfHarfBuzzTextShaper, die libharfbuzz tijdens runtime bindt zodat een ontbrekende bibliotheek een gemelde toestand is in plaats van een lafstoring
Eén interface, twee backends die het werk volkomen anders verdelen. Die asymmetrie begrijpen is wat verhindert dat het draagbare pad tekst oplevert die correct gevormd en verkeerd gepositioneerd is
Waarom is de Windows-backend één klasse en de draagbare drie stukken?
Omdat Uniscribe vier API's is die doen alsof ze er één zijn. ScriptItemize segmenteert een string per schrift en resolveert bidirectionele niveaus; ScriptShape mapt tekens op glyphs; ScriptPlace berekent advances en offsets; ScriptLayout zet de resulterende runs in visuele volgorde. Een backend daarop gebouwd heeft dus niets meer toe te voegen, en daarom is de Windows-shaper één klasse met één methode
HarfBuzz dekt de middelste twee. Die vormt en plaatst een run waarvan de aanroeper de richting en het schrift al heeft beslist, en die heeft geen mening over hoe een alinea in runs splitst of in welke volgorde die runs verschijnen. De draagbare backend levert dus de rest: het bidirectionele algoritme resolveert embeddingniveaus, de HarfBuzz-Unicodefuncties segmenteren de tekst per schrift, en de runs worden gelegd in de visuele volgorde die regel L2 van UAX #9 oplevert. De bidirectionele helft is substantieel genoeg voor een eigen unit, beschreven in het artikel over UAX #9 embeddingniveaus
De shaper resolveert geen fonts, en dat is opzettelijk
Uniscribe leest het fontbinair bestand uit een GDI-device context. Er is geen draagbaar equivalent van, en er één verzinnen binnen een shaping-unit zou betekenen dat namens elke applicatie wordt beslist of fonts uit fontconfig komen, uit CoreText, uit een fontmap van de applicatie of uit een database. Daarom neemt de HarfBuzz-backend een resolver: een callback die een fontnaam mapt op de TrueType- of OpenType-bytes. False teruggeven laat het shaping-verzoek falen op dezelfde manier waarop een onleesbaar GDI-font dat onder Windows laat falen
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// Uw beleid: fontconfig, CoreText, een fontmap van de app, een database
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// Eigendom gaat naar de unit; roep één keer aan tijdens de opstart,
// voordat er ook maar iets tekst vormt
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// Onder Delphi wordt de platformstandaard (Uniscribe) op verzoek aangemaakt,
// dus helemaal geen installatie nodig
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
Fontontdekking buiten de shaper houden heeft een tweede voordeel dat zich in servers toont: hetzelfde proces kan vormen met een ingebedde fontset die niets te maken heeft met wat op de machine is geïnstalleerd, en dat is wat u wilt wanneer de uitvoer byte-reproduceerbaar moet zijn over hosts heen. De component stelt ook een fontprovider van het hostsysteem bloot voor de gevallen waarin u wél geïnstalleerde fonts wilt, behandeld in het artikel over de systeemfontprovider
Het resultaatrecord is backend-neutraal, en clusters zijn de reden
Beide backends vullen dezelfde TPdfShapedText in: de brontekst, fontnaam, grootte, fontbytes, een array van runs, de totale breedte, het glyphaantal en het logische tekenaantal. Elke TPdfShapedRun draagt zijn reikwijdte in de brontekst, zijn visuele X-positie, zijn breedte, zijn bidirectioneel niveau en een vlag rechts-naar-links, plus zijn glyphs. Elke TPdfShapedGlyph draagt een glyph-identificator, een advance, X- en Y-offsets en de cluster waartoe die behoort als een begin en een lengte in de brontekst
Die clustervelden zijn wat het record bruikbaar maakt in plaats van slechts informatief. Shaping is geen één-op-één-mapping: een Devanagari-lettergreep wordt één glyph uit vier tekens, een Arabische ligatuur voegt er twee samen, en één teken kan verschillende diakritische tekens opleveren. Zonder clusterspans kunt u geen caret plaatsen, een klik hit-testen of een selectie markeren, want u kunt niet zeggen bij welke tekens een glyph hoort. Met die velden is de rekenkunde lokaal en werkt dezelfde code voor beide 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
// Runs komen al binnen in visuele volgorde met VisualX ingevuld
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;
Budgets horen in het optierecord
TPdfTextShapingOptions draagt een richting mee plus drie caps: maximaal aantal tekens, maximaal aantal glyphs en maximaal aantal runs, met een klassenfunctie Default die zinnige waarden invult. De caps zijn geen paranoïde over misvormde input; het is rekenkunde. Shaping zet uit: een font met agressieve contextuele substitutie kan meer glyphs uitzenden dan invoertekens, en een alinea die om de paar tekens van schrift wisselt levert een run per wisseling op. Een document dat zo is opgebouwd om beide te maximaliseren maakt van een bescheiden string een grote toewijzing, en een dienst die tekst uit onbetrouwbare PDFs vormt heeft een limiet nodig die die zelf koos in plaats van een limiet die de machine oplegt
De richting expliciet instellen in plaats van die op automatisch te laten is het doen waard zodra u die al weet. Automatisch past de regels voor alineabrichting toe om te gokken vanuit het eerste sterke teken, wat juist is voor vrije tekst en fout voor een formulierveld waarvan de richting een eigenschap van het veld is in plaats van van de waarde die iemand erin typte
Binding tijdens runtime, geen buildafhankelijkheid
De HarfBuzz-backend laadt de bibliotheek dynamisch. Dat is een uitrolbeslissing met echte gevolgen: één binary draait op een machine met HarfBuzz en op een machine zonder, en meldt in het tweede geval verminderde capaciteit in plaats van niet op te starten. Voor een bibliotheek die aan andere ontwikkelaars wordt geleverd is dat de enige werkbare opzet, want u kunt niet van elke verbruiker van een PDF-component eisen dat die een shaping-bibliotheek aanschaft en op versie matcht die die misschien niet nodig heeft
De bijbehorende regel voor aanroepers is controleren. ActiveTextShaper geeft nil terug wanneer het platform geen standaard heeft en er geen is ingesteld, en het shaping-toegangspunt meldt dat als een niet-beschikbare shaper in plaats van als een shaping-fout. Dat zijn verschillende problemen en ze verdienen verschillende meldingen: de ene is een uitrolgat, de andere is een font- of tekstprobleem
Installeer één keer, voordat er iets vormt
De installatie vervangt de vorige shaper en geeft die vrij, dus herhaald aanroepen is veilig maar zinloos, en aanroepen terwijl een andere thread aan het vormen is, is helemaal niet veilig. Doe het tijdens de opstart. Moet u later terugvallen op de platformstandaard, geef dan nil door, en dat is ook hoe u een testdubbel aan het eind van een test ongedaan maakt
Zodra een backend is geïnstalleerd, gedragen meten en afbreken zich hetzelfde op beide platforms, omdat ze de run- en glyphmetrieken verbruiken in plaats van het platform rechtstreeks aan te roepen; het afbreekmodel wordt beschreven in het artikel over tekstmeting en woordafbreking. Ondersteunde platforms en toolchains voor de component staan op de productpagina van de PDFium Delphi component