Teknisk artikel

Bädda in CID Type 2-typsnitt i PDF från Delphi

PDFium Component skriver text till en PDF-sida på glyfnivå med AddCidType2Text, och bäddar in TrueType-data du tillhandahåller som ett CID Type 2-typsnitt. Den tilldelar varje glyfinstans en sekventiell CID, genererar den explicita CID-till-GID-mappningen och ToUnicode-CMap:en, och slår ihop glyfer på samma baslinje till ett enda inbyggt textobjekt. Valfri delmängdsbildning håller filen liten, med en explicit policy för vad som händer när delmängdsbildningen misslyckas

Skrivning på glyfnivå är vad du behöver när texten redan har formats. Arabiska, devanagari och varje skriftsystem med kontextberoende former producerar en sekvens av glyfidentifierare som inte längre mappar en-till-en mot tecken, så ett API som tar en sträng och ett typsnittsnamn kan inte uttrycka resultatet. Att direkt lämna över glyfer och positioner är det enda sättet att få korrekt formad text i komplexa skriftsystem in i en PDF

AddCidType2Text i Delphi tilldelar varje formad glyfinstans ett sekventiellt CID, skriver CIDToGIDMap och ToUnicode-CMap, och slår samman sekvensen till ett enda sidtextobjekt som extraheras rent
Varje glyfinstans tar sitt eget CID, typsnittsordlistan mappar CIDs till glyf-id:n och tillbaka till Unicode, och sekvensen landar på sidan som ett textobjekt som extraherar tillbaka till avsedd text

Varför varje glyfinstans får sin egen CID

Den lockande optimeringen är att avdublettera: en CID per unik glyfidentifierare, återanvänd varhelst den glyfen förekommer. Det ger ett mindre typsnitt och det bryter textextraktion, eftersom samma glyf legitimt kan motsvara olika Unicode-innehåll på olika ställen

En ligaturglyf är det tydliga fallet. Samma "ffi"-glyf kan stå för de tre tecknen i ett ord och, efter ett annat formningsbeslut, för något annat på ett annat ställe. ToUnicode-mappningen är nyckelbaserad på CID, så en delad CID kan bara bära en mappning, och vilken text som än förlorar den tävlingen blir oextraherbar

Så varje glyfinstans tar sin egen CID, och varje mappning är fri att vara flera UTF-16-kodenheter. En glyf utan meningsfull text — ett dekorativt element, en glyf enbart för positionering — mappas explicit till U+200B, ett nollbrett mellanslag, så att varje CID har ett observerbart extraktionsresultat snarare än ett hål

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;

    // En post per formad glyf: glyf-id:t, texten den representerar,
    // samt dess förskjutning och offset i textrymden
    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;   // subset preferred
    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ängdsbildning, och de kontroller en returkod inte ger dig

Att delmängdsbilda ett typsnitt till de glyfer som faktiskt används är skillnaden mellan att bädda in 300 KB och att bädda in 12 KB, och på ett dokument med flera typsnitt avgör det om filen går att e-posta. Plattformens delmängdsbildningsväg accepterar en glyflista direkt, vilket passar detta API perfekt eftersom anroparen redan känner till varje glyfidentifierare som används

Vad det inte ger dig är säkerhet. Ett delmängdsbildningsanrop kan rapportera framgång och returnera utdata som är oanvändbar, så komponenten validerar tre egenskaper innan resultatet accepteras: utdatan måste vara mindre än originalet, den måste tolkas om som en giltig sfnt med en läsbar maxp-tabell, och den måste behålla glyfer upp till den högsta ursprungliga identifieraren som begärts. Varje misslyckande betyder att delmängden avvisas

Vad som händer härnäst är anroparens policy. Under pcfemSubsetPreferred, standardvärdet, faller en avvisad delmängd tillbaka till att bädda in hela typsnittet, så sidan är korrekt och bara större. Under pcfemSubsetRequired misslyckas operationen innan sidan ändras, vilket är vad en storleksbegränsad pipeline vill ha. pcfemFull hoppar över delmängdsbildning helt. Rapporten talar om för dig vilken väg som togs via SubsetAttempted, SubsetApplied, UsedFullFontFallback och SubsetErrorCode

CID Type 2-typsnittsdelmängdning i Delphi passerar tre valideringskontroller för storlek, sfnt-integritet och glyftäckning före acceptans, och en avvisad delmängd följer pcfem-policyn med reserv av fullständigt typsnitt eller hårt misslyckande
En delmängd godtas först efter att storleks-, sfnt- och glyftäckningskontrollerna godkänts, och en avvisad delmängd följer sedan anroparens policy — fall tillbaka till hela typsnittet eller fallera innan sidan ändras

Verifiering som inte kan ge ett falskt positivt

Med VerifyExtraction aktiverat bekräftar komponenten att det den skrev kan läsas tillbaka. Det naiva sättet att göra det är att extrahera hela sidan och söka efter den förväntade strängen, och det är fel: en sida som redan innehöll den texten klarar kontrollen även när den nya texten skrevs felaktigt

Istället byggs textsidan om och de objekthandtag som infogats av detta anrop läses individuellt, i infogningsordning, och sammanfogas. Resultatet jämförs mot den förväntade texten, och rapporten exponerar båda strängarna tillsammans med det booleska värdet, så en avvikelse kan diagnostiseras snarare än bara upptäckas

Slå på det under utveckling och i varje pipeline där extraherbarhet är ett krav — sökbara arkiv, tillgänglighetsefterlevnad, nedströms textutvinning. Kostnaden är en omgjord textsida per anrop, vilket är varför det inte är påslaget som standard i täta loopar

Budgetar och atomärt misslyckande

Typsnittsbyte, glyfinstanser och Unicode-kodenheter begränsas var för sig innan allokering, och typsnittsformatet, TTC-indexet, glyfidentifierarintervallet och geometrivärdena valideras innan något skrivs. Geometrin måste vara ändlig, vilket låter uppenbart tills en formningsmotor ger dig en NaN-förskjutning från ett felformat typsnitt

Misslyckande är atomärt på sidnivå. Om typsnittsladdning, objektskrivning, innehållsgenerering eller extraktionsverifiering misslyckas tas varje objekt som infogats av det anropet bort i omvänd ordning och sidinnehållet återgenereras. Ett misslyckat anrop lämnar sidan som den var, inte med en halv textkörning på den

Var detta passar in i en textpipeline

Arbetsfördelningen är värd att ange rakt av. Formning — att omvandla tecken till positionerade glyfer — är inte detta API:s jobb; det hör till en formningsmotor, och komponentens egna stöd för bidirektionell text och komplexa skriftsystem täcks i hantering av emoji, CJK och surrogatpar. Det här API:et är vad du anropar efteråt, med den glyfkörning formningsmotorn producerade

För vanlig latinsk text utan kontextberoende formning är de enklare strängbaserade text-API:erna rätt verktyg och ger mindre kod. Ta till CID Type 2-skrivning när du har formad utdata, när du behöver exakt kontroll över glyfidentifierare, eller när typsnittet måste bäddas in från byte du redan har snarare än lösas upp via namn — typsnittsupplösningsalternativet är leverantörsmekanismen som beskrivs i att styra PDF-typsnittssubstitution

En driftsättningsanmärkning: att bädda in ett typsnitt är lika mycket en licensfråga som en teknisk fråga. Typsnitt varierar i om inbäddning är tillåten alls, tillåten enbart för visning, eller tillåten för redigering. Biblioteket bäddar in vilka byte du än ger det, och att kontrollera licensen är ditt ansvar, inte filformatets

Textskrivning på glyfnivå, typsnittsförsörjning och textextraktion delar samma sidmodell i Delphi, C++Builder och Lazarus; det fullständiga API:et beskrivs på sidan för PDFium Component för Delphi