Teknisk artikel

Indlejr CID Type 2-skrifttyper i PDF fra Delphi

PDFium Component skriver tekst ind i en PDF-side på glyfniveau med AddCidType2Text og indlejrer TrueType-data, du selv leverer, som en CID Type 2-skrifttype. Den tildeler hver glyfinstans et sekventielt CID, genererer det eksplicitte CID-til-GID-kort og ToUnicode CMap'et, og samler glyffer på samme baseline til ét enkelt nativt tekstobjekt. Valgfri delmængdedannelse holder filen lille, med en eksplicit politik for, hvad der sker, når delmængdedannelse fejler

Skrivning på glyfniveau er, hvad du har brug for, når teksten allerede er formet (shaped). Arabisk, devanagari og ethvert skriftsystem med kontekstuelle former producerer en sekvens af glyfidentifikatorer, der ikke længere kortlægger én-til-én til tegn, så et API, der tager en streng og et skrifttypenavn, kan ikke udtrykke resultatet. At overdrage glyffer og positioner direkte er den eneste måde at få korrekt formet kompleks skrifttekst ind i en PDF

AddCidType2Text i Delphi tildeler hver shaped glyfinstans et sekventielt CID, skriver CIDToGIDMap og ToUnicode CMap og fletter forløbet ind i ét sidetekstobjekt, der udtrækker rent
Hver glyf-instans tager sin egen CID, fontordbogen afbilder CIDs til glyph-id'er og tilbage til Unicode, og forløbet lander på siden som ét tekstobjekt, der ekstraheres tilbage til den tilsigtede tekst

Hvorfor hver glyfinstans får sit eget CID

Den fristende optimering er at deduplikere: ét CID pr. distinkt glyfidentifikator, genbrugt hvor end den glyf optræder. Det giver en mindre skrifttype, og det ødelægger tekstudtrækning, fordi den samme glyf legitimt kan svare til forskelligt Unicode-indhold forskellige steder

En ligatur-glyf er det klare eksempel. Den samme "ffi"-glyf kan stå for de tre tegn i ét ord og, efter en anden shaping-beslutning, for noget andet et andet sted. ToUnicode-kortet er nøglet efter CID, så et delt CID kan kun bære én kortlægning, og hvilken tekst, der taber den kamp, bliver ikke-udtrækbar

Så hver glyfinstans tager sit eget CID, og hver kortlægning står frit til at være flere UTF-16-kodeenheder. En glyf uden meningsfuld tekst — et dekorativt element, en glyf til ren positionering — kortlægges eksplicit til U+200B, et nulbreddemellemrum, så hvert CID har et observerbart udtrækningsresultat frem for et hul

uses
  PDFium;

var
  Pdf: TPdf;
  Glyphs: TPdfCidGlyphs;
  Options: TPdfCidFontOptions;
  Report: TPdfCidFontReport;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'label.pdf';
    Pdf.LoadDocument;
    Pdf.PageNumber := 1;

    // Én post pr. formet glyf: glyf-id'et, teksten den repræsenterer,
    // og dens advance og offsets i tekstrum
    SetLength(Glyphs, 3);
    Glyphs[0].GlyphID := 402;  Glyphs[0].UnicodeText := 'ffi';
    Glyphs[0].Advance := 18.4;
    Glyphs[1].GlyphID := 71;  Glyphs[1].UnicodeText := 'c';
    Glyphs[1].Advance := 9.8;
    Glyphs[2].GlyphID := 74;   Glyphs[2].UnicodeText := 'e';
    Glyphs[2].Advance := 9.2;

    Options := TPdfCidFontOptions.Default;   // delmængde foretrukket
    Options.VerifyExtraction := True;

    if Pdf.AddCidType2Text(LoadFileBytes('NotoSans.ttf'), Glyphs,
         11, 72, 700, Options, Report) then
      Writeln(Format('%d glyphs, %d unique, font %d -> %d bytes, match=%s',
        [Report.GlyphCount, Report.UniqueGlyphCount,
         Report.OriginalFontBytes, Report.EmbeddedFontBytes,
         BoolToStr(Report.ExtractionMatches, True)]));
  finally
    Pdf.Free;
  end;
end;

Delmængdedannelse, og de tjek en returkode ikke giver dig

At danne en delmængde af en skrifttype til de glyffer, der faktisk bruges, er forskellen mellem at indlejre 300 KB og at indlejre 12 KB, og på et dokument med flere skrifttyper afgør det, om filen kan sendes med e-mail. Platformens delmængdedannelsessti accepterer en glyfliste direkte, hvilket passer perfekt til dette API, da den kaldende part allerede kender hver glyfidentifikator i brug

Hvad det ikke giver dig, er sikkerhed. Et delmængdedannelseskald kan rapportere succes og returnere output, der er ubrugeligt, så komponenten validerer tre egenskaber, før den accepterer resultatet: outputtet skal være mindre end originalen, det skal kunne genparses som en gyldig sfnt med en læsbar maxp-tabel, og det skal beholde glyffer op til den højeste oprindelige identifikator, der blev anmodet om. Enhver fejl betyder, at delmængden afvises

Hvad der sker dernæst, er den kaldende parts politik. Under pcfemSubsetPreferred, standarden, falder en afvist delmængde tilbage til at indlejre den fulde skrifttype, så siden er korrekt og blot større. Under pcfemSubsetRequired fejler operationen, før siden ændres, hvilket er, hvad en størrelsesbegrænset pipeline ønsker. pcfemFull springer delmængdedannelse helt over. Rapporten fortæller dig, hvilken sti der blev taget, via SubsetAttempted, SubsetApplied, UsedFullFontFallback og SubsetErrorCode

CID Type 2 font subsetting i Delphi passerer tre valideringskontroller for størrelse, sfnt-integritet og glyfdækning før accept, og et afvist subset følger pcfem-politikken om fuld skrifttype-fallback eller hård fejl
En subset accepteres først, efter størrelses-, sfnt- og glyfdækningstjekkene består, og en afvist subset følger derefter kalderens politik — fald tilbage til den fulde font eller fejle, før siden ændres

Verifikation, der ikke kan give et falsk positivt

Med VerifyExtraction slået til bekræfter komponenten, at det, den skrev, kan læses tilbage. Den naive måde at gøre det på er at udtrække hele siden og søge efter den forventede streng, og det er forkert: en side, der allerede indeholdt den tekst, består tjekket, selv når den nye tekst blev skrevet forkert

I stedet genopbygges tekstsiden, og de objekthandtag, dette kald indsatte, læses individuelt, i indsættelsesrækkefølge, og sammenkædes. Resultatet sammenlignes med den forventede tekst, og rapporten eksponerer begge strenge sammen med boolean'en, så en uoverensstemmelse kan diagnosticeres frem for blot registreres

Slå det til under udvikling og i enhver pipeline, hvor udtrækbarhed er et krav — søgbare arkiver, tilgængelighedsoverholdelse, downstream tekstmining. Omkostningen er én tekstside-genopbygning pr. kald, hvilket er grunden til, at det ikke er slået til som standard i tætte løkker

Budgetter og atomar fejl

Skrifttype-bytes, glyfinstanser og Unicode-kodeenheder er hver især begrænset før allokering, og skrifttypeformatet, TTC-indekset, glyfidentifikator-intervallet og geometriværdierne valideres, før noget skrives. Geometri skal være finit, hvilket lyder indlysende, indtil en shaping-motor overdrager dig en NaN-advance fra en fejlformet skrifttype

Fejl er atomar på sideniveau. Hvis skrifttypeindlæsning, objektskrivning, indholdsgenerering eller udtrækningsverifikation fejler, fjernes hvert objekt, dette kald indsatte, i omvendt rækkefølge, og sideindholdet genereres på ny. Et mislykket kald efterlader siden, som den var, ikke med en halv tekstrun på den

Hvor dette passer ind i en tekstpipeline

Arbejdsdelingen er værd at sige ligeud. Shaping — at omdanne tegn til placerede glyffer — er ikke dette API's job; det hører til en shaping-motor, og komponentens egen bidirektionelle og kompleks-skrift-understøttelse er dækket i håndtering af emoji, CJK og surrogatpar. Dette API er, hvad du kalder bagefter, med det glyf-run, shaperen producerede

For almindelig latinsk tekst uden kontekstuel shaping er de simplere streng-baserede tekst-API'er det rigtige værktøj og giver mindre kode. Grib til CID Type 2-skrivning, når du har formet output, når du har brug for præcis kontrol af glyfidentifikatorer, eller når skrifttypen skal indlejres ud fra bytes, du selv holder, frem for opløst ved navn — alternativet med skrifttypeopløsning er den provider-mekanisme, der er beskrevet i styring af PDF-skrifttypesubstitution

Én bemærkning om produktionsbrug: at indlejre en skrifttype er lige så meget et licensspørgsmål som et teknisk et. Skrifttyper varierer i, om indlejring overhovedet er tilladt, kun tilladt til visning, eller tilladt til redigering. Biblioteket vil indlejre hvilke bytes, du end giver det, og det er dit ansvar at tjekke licensen, ikke filformatets

Tekstskrivning på glyfniveau, skrifttypeforsyning og tekstudtrækning deler den samme sidemodel i Delphi, C++Builder og Lazarus; det fulde API er beskrevet på PDFium Component for Delphi-siden