losLab PDF Library kan de ontbrekende lettertypeprogramma's van een reeds geladen PDF insluiten met een enkele aanroep: EmbedMissingFonts doorloopt elk lettertypewoordenboek in het document, zoekt het bijbehorende geïnstalleerde systeemlettertype op basis van de BaseFont-naam en schrijft het lettertypeprogramma terug in het bestand. Voor teams die documenten van derden repareren die de PDF/A-validatie op lettertype-insluiting niet doorstaan, is dit de oplossing die preflight-fout 00030 doet verdwijnen
Dit scenario komt helaas vaak voor. Een archiefverwerkingspijplijn ontvangt PDF's van leveranciers, klanten of een scanbureau; the documenten worden op elk bureau in het gebouw prima weergegeven; en vervolgens keurt de PDF/A-validator de hele batch af met dezelfde klacht die eenmaal per bestand wordt herhaald: er is ten minste één lettertype niet ingesloten. Niemand stroomopwaarts zal de bestanden opnieuw genereren, dus moet de pijplijn ze repareren. Dit artikel behandelt dat reparatiepad. Het is de aanvulling op het preflight-artikel, dat het detecteren van PDF/A- en PDF/UA-schendingen behandelt: dat deel vertelt u welke documenten kapot zijn, dit deel repareert de meest voorkomende manier waarop ze kapot zijn
Waarom vereist PDF/A dat elk lettertype is ingesloten?
ISO 19005-1 §6.3.4 vereist dat elk lettertype dat door een conform document wordt gebruikt, zijn lettertypeprogramma in het bestand meedraagt, omdat de belofte van PDF/A volledig draait om reproduceerbaarheid: het document moet over vijftig jaar identiek worden weergegeven op een machine die geen lettertypen deelt met de machine die het heeft geproduceerd. Een niet-ingesloten lettertype is een instructie om ergens op het weergavesysteem naar Arial te zoeken, en het standpunt van de standaard is dat "ergens op het weergavesysteem" geen archiveringsgarantie is. Welke glyphs, statistieken en dekking het vervangende lettertype ook heeft, dat is wat de lezer te zien krijgt, en het kan afwijken van wat de auteur zag
De historische boosdoener is de Standard 14-conventie. PDF 1.0 beloofde dat elke viewer Helvetica, Times, Courier, Symbol en ZapfDingbats meelevert, dus leerden generatoren om naar die lettertypen te verwijzen op naam en niets in te sluiten, en dertig jaar aan tools doet nog steeds precies dat. losLab PDF Library neemt deze vereiste zo serieus dat in de PDF/A-aanmaakmodus AddStandardFont bewust een no-op is: de bibliotheek levert de Standard 14-lettertypeprogramma's niet mee, kan niet insluiten wat ze niet heeft, en weigert een niet-ingesloten verwijzing te schrijven naar een document dat beweert conform te zijn. Het retourneert 0 zonder een lettertype te selecteren, dus moet een PDF/A-document in plaats daarvan AddTrueTypeFont met insluiting gebruiken, en elk verzoek voor Embed=0 wordt stilletjes gepromoveerd tot Embed=1 terwijl de PDF/A-modus actief is. Dat is de kant van de schrijver. Het moeilijkere probleem is de kant van de lezer: een document dat iemand anders al heeft geschreven, vol met lettertypewoordenboeken die u niet hebt gemaakt
Hoe repareert EmbedMissingFonts een geladen document?
losLab PDF Library repareert lettertypen ter plaatse in plaats van ze opnieuw op te bouwen. Wanneer een PDF-generator een niet-ingesloten TrueType-lettertype schrijft, is het geproduceerde FontDescriptor-woordenboek al compleet: FontName, FontBBox, Flags, Ascent, Descent, StemV, allemaal aanwezig. Het enige dat het scheidt van een ingesloten lettertype is de afwezigheid van één item, de streamverwijzing /FontFile2 die het werkelijke lettertypeprogramma bevat. Dus EmbedMissingFonts raakt het lettertypewoordenboek, de codering, de breedtetabel of enige inhoudsstroom die naar het lettertype verwijst via de resourcenaam niet aan. Het leest het overeenkomstige lettertypeprogramma van het systeem, comprimeert het in een nieuw streamobject en voegt een enkele /FontFile2-verwijzing (of /FontFile3 voor CIDFontType0-lettertypen) toe aan de FontDescriptor die er al staat. Alles waarnaar de pagina's van het document verwijzen blijft precies waar het was, wat de bewerking veilig maakt om uit te voeren op bestanden die u niet beheert
De dekking omvat beide lettertype-architecturen die u in de praktijk tegenkomt: eenvoudige TrueType-lettertypen en samengestelde Type0/CID-lettertypen, het soort dat wordt geproduceerd voor CJK-tekst en moderne Unicode-uitvoer. De doorloop doorloopt bewust elk Font-woordenboek in de objectstructuur van het document in plaats van te vertrouwen op een paginaspecifieke doorloop van bronnen, zodat lettertypen waarnaar wordt verwezen vanuit annotaties of die over pagina's worden gedeeld ook worden meegenomen. De API is een enkele aanroep op het geladen document
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// Walks every Font dictionary; returns how many fonts
// gained a font program. Fonts whose program cannot be
// found on the system are skipped, not failed.
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
De reparatie verifiëren met een preflight-rapport
CreatePreflightReport is de verificatiestap, en de cirkel is bewust rond: dezelfde audit die het bestand heeft afgekeurd, moet ook degene zijn die het goedkeurt. Foutcode 00030 is the PDF/A deep-auditbevinding die luidt "Er is ten minste één lettertype niet ingesloten (FontFile/FontFile2/FontFile3 ontbreekt)", en dit wordt gerapporteerd voor het bestand als geheel, dus een enkele over het hoofd gezien lettertype houdt de fout in stand. Voer het rapport uit op het bronbestand, repareer, sla op en voer het opnieuw uit op het uitvoerbestand
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 selects the PDF/A checks
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
Laad het gerepareerde document opnieuw en doorloop het voor een weergave per lettertype in plaats van een oordeel per bestand: FindFonts gevolgd door SelectFont en GetFontIsEmbedded rapporteert de insluitingsstatus lettertype voor lettertype, wat het juiste hulpmiddel is wanneer een batchtaak exact moet loggen welk lettertype in welk bestand niet kon worden gerepareerd. Hetzelfde enumeratiepatroon verschijnt in het artikel over het extraheren van tekst, afbeeldingen en lettertypen uit geladen PDF's, waar het dient voor extractie in plaats van reparatie
Wat gebeurt er als het lettertype niet op het systeem is geïnstalleerd?
EmbedMissingFonts slaat elk lettertype over waarvan het het programma niet kan vinden, en rapporteert de overslaande actie via de retourwaarde: als de telling lager uitvalt dan het aantal niet-ingesloten lettertypen dat u hebt geteld, is het verschil de lettertypen die het systeem niet heeft. Dit is de eerlijke foutmodus en deze is beter dan de alternatieven, omdat het bedenken van een vervangend programma voor een lettertype dat in het document wordt genoemd de weergave zou veranderen, wat een archiveringsreparatie juist nooit mag doen. Voor deze gevallen biedt losLab PDF Library EmbedFontProgramFromFile, waarmee een door de aanroeper geleverd .ttf- of .otf-bestand in het genoemde lettertype wordt ingesloten, zodat een pijplijn de huisstijllettertypen kan meesturen die hij verwacht tegen te komen en daar bewust op kan terugvallen
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// Try the installed system font first, then fall back
// to a font file shipped alongside the application
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
Twee grenzen moeten duidelijk worden aangegeven. Ten eerste worden Type1-lettertypen in de huidige implementatie niet gerepareerd: hun /FontFile-item vereist de driesegmenten-PFB-structuur met expliciete lengtesleutels, en de bibliotheek slaat ze over in plaats van een misvormde stream te schrijven; ze zijn zeldzaam in moderne documenten, maar ze komen voor in oude archieven. Ten tweede is het insluiten van een lettertype een licentiehandeling. De insluitingsrechten van een TrueType-lettertype behoren toe aan de leverancier, en een reparatiepijplijn die gelicentieerde lettertypeprogramma's stopt in documenten die de organisatie verlaten, moet door iemand laten controleren of de lettertyplicenties dit daadwerkelijk toestaan. De bibliotheek doet wat u vraagt; of u dat mag vragen is een vraag voor uw juridische afdeling, niet voor uw compiler
Insluiten is noodzakelijk, maar niet voldoende
Het repareren van lettertypen lost alleen fout 00030 op en niets anders. Een document dat mislukt voor PDF/A op basis van versleuteling, ontbrekende XMP-metadata, een apparaatafhankelijke kleurruimte zonder een OutputIntent, of ontbrekende ToUnicode-tabellen, zal nog steeds mislukken nadat elk lettertype is ingesloten. Daarom hoort de reparatie thuis in een preflight-gestuurde lus in plaats van deze te vervangen. Voer het volledige rapport uit, repareer wat het aangeeft en laat het rapport u vertellen wanneer u klaar bent. Er is ook een kostenaspect: een volledig CJK-lettertypeprogramma kan oplopen tot megabytes, dus het insluiten van meerdere van deze programma's kan een klein document drastisch vergroten. Het tegengewicht is subsetting, behandeld in het artikel over PDF-bestandsgrootte-optimalisatie en lettertype-subsetting, dat elk ingesloten programma beperkt tot de glyphs die het document daadwerkelijk weergeeft
Voorkomen dat nieuwe documenten achteruitgaan
SetEmbedAllFonts is de helft ter voorkoming van dezelfde functie: een beveiliging aan de schrijverszijde die voorkomt dat uw eigen code de documenten produceert die in dit artikel worden gerepareerd. Als SetEmbedAllFonts(1) actief is, wordt elke volgende AddTrueTypeFont-aanroep die vraagt om Embed=0 gepromoveerd tot een ingesloten verwijzing, wat de garantie die de PDF/A-modus al afdwingt uitbreidt naar elk document. Het heeft invloed op lettertypen die na de aanroep worden toegevoegd, niet op lettertypen die al in een geladen bestand aanwezig zijn, dus de taakverdeling is duidelijk: SetEmbedAllFonts voor de documenten die u maakt, EmbedMissingFonts voor de documenten die u erft
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file
Beide helften, de beveiliging aan de schrijverszijde en het laad-repareer-bewaar-pad, maken deel uit van losLab PDF Library voor Delphi, C# en VB.NET, samen met de preflight-engine die het resultaat verifieert; de productpagina bevat de volledige referentie van de lettertype-API, inclusief de aanroepen voor insluiting en subsetting per lettertype