In zakelijke print-, archiverings- en documentcomplianceworkflows kan een PDF-bestand niet zomaar worden "gerenderd". Het moet worden gecontroleerd (ge-audit). Een PDF kan niet-afgevlakte transparantiegroepen bevatten die oudere raster image processors (RIP's) doen crashen, ingesloten JavaScript dat een veiligheidsrisico vormt, of afbeeldingen met een lage resolutie die er vreselijk uitzien wanneer ze op 300 DPI worden afgedrukt.
Dit proces van het inspecteren van een PDF voordat deze in een productieworkflow terechtkomt, staat bekend als Preflighting. In dit artikel onderzoeken we hoe u een geautomatiseerde preflight- en risicocontroletool in Delphi kunt bouwen door gebruik te maken van de low-level parsingmogelijkheden van PDFium.
Belangrijkste preflight-controles
Een standaard risicocontrole (audit) controleert doorgaans op de volgende elementen binnen een PDF:
- Interactieve elementen: JavaScript-acties, Start-acties (uitvoeren van externe programma's) en AcroForms.
- Middelmetrieken: Aanwezigheid van afbeeldingen met een lage resolutie of ontbrekende ingesloten lettertypen.
- Beveiligingsstatus: Documentversleuteling, wachtwoordvereisten en toegangsrechten (bijv. afdrukken uitgeschakeld).
- Documentstandaarden: Validatie van PDF/A- of PDF/X-conformiteitsmarkeringen in de documentmetadata.
PDF-metadata en annotaties extraheren met PDFium
PDFium biedt een robuuste C API die Delphi kan gebruiken. Om een preflight-audit uit te voeren, renderen we niet alleen de pagina; we doorlopen de objectboom van de PDF. Laten we eens kijken hoe we door de documentpagina's kunnen itereren en annotaties kunnen inspecteren om potentieel risicovolle JavaScript-acties te vinden.
uses
System.SysUtils, pdfium_lib;
procedure AuditPdfSecurity(const FileName: string);
var
Doc: FPDF_DOCUMENT;
PageCount, i, j: Integer;
Page: FPDF_PAGE;
AnnotCount: Integer;
Annot: FPDF_ANNOTATION;
AnnotSubType: FPDF_ANNOTATION_SUBTYPE;
Action: FPDF_ACTION;
ActionType: ULONG;
begin
FPDF_InitLibrary();
try
// Load the document without a password
Doc := FPDF_LoadDocument(PAnsiChar(AnsiString(FileName)), nil);
if Doc = nil then
raise Exception.Create('Failed to load document or password required.');
try
PageCount := FPDF_GetPageCount(Doc);
Writeln(Format('Auditing %d pages...', [PageCount]));
for i := 0 to PageCount - 1 do
begin
Page := FPDF_LoadPage(Doc, i);
if Page <> nil then
begin
AnnotCount := FPDFPage_GetAnnotCount(Page);
for j := 0 to AnnotCount - 1 do
begin
Annot := FPDFPage_GetAnnot(Page, j);
if Annot <> nil then
begin
AnnotSubType := FPDFAnnot_GetSubtype(Annot);
// Check for Link Annotations that might have malicious actions
if AnnotSubType = FPDF_ANNOT_LINK then
begin
Action := FPDFAnnot_GetLinkAction(Annot);
if Action <> nil then
begin
ActionType := FPDFAction_GetType(Action);
// 2 represents PDFACTION_URI, 3 represents PDFACTION_SOUND, 4 represents PDFACTION_MOVIE
// We specifically look out for PDFACTION_LAUNCH or PDFACTION_JAVA_SCRIPT
if (ActionType = PDFACTION_JAVA_SCRIPT) or (ActionType = PDFACTION_LAUNCH) then
begin
Writeln(Format('WARNING: Malicious action detected on page %d', [i + 1]));
end;
end;
end;
FPDFPage_CloseAnnot(Annot);
end;
end;
FPDF_ClosePage(Page);
end;
end;
finally
FPDF_CloseDocument(Doc);
end;
finally
FPDF_DestroyLibrary();
end;
end;
Afbeeldingsresolutie controleren
Een andere kritieke preflight-stap voor printworkflows is ervoor zorgen dat geen enkele afbeelding onder een specifieke DPI-drempel valt. Met PDFium kunt u afbeeldingsobjecten rechtstreeks uit de paginastream extraheren. Door de pixelafmetingen van de geëxtraheerde afbeelding te delen door de fysieke afmetingen (in punten) die deze op de PDF-pagina inneemt, kunt u de effectieve DPI berekenen.
procedure AuditPageImages(Page: FPDF_PAGE);
var
ObjCount, i: Integer;
PageObj: FPDF_PAGEOBJECT;
ImgWidth, ImgHeight: Integer;
begin
ObjCount := FPDFPage_CountObjects(Page);
for i := 0 to ObjCount - 1 do
begin
PageObj := FPDFPage_GetObject(Page, i);
if FPDFPageObj_GetType(PageObj) = FPDF_PAGEOBJ_IMAGE then
begin
FPDFImageObj_GetBitmap(PageObj); // Returns an FPDF_BITMAP you can inspect
// Retrieve metadata using FPDFImageObj_GetImageMetadata
// Calculate effective DPI based on quad coordinates
end;
end;
end;
Integratie met PDFium Component
Het bouwen van een robuuste preflight-engine vanaf nul met behulp van de ruwe PDFium C API vereist aanzienlijke boilerplate-code en diepgaande kennis van de PDF-specificatie. Het gebruik van een wrapper vereenvoudigt dit enorm. Met een high-level Delphi wrapper kunt u complexe C-pointer-iteraties omzetten in schone objectgeoriënteerde code.
Door de preflight-fase te automatiseren, voorkomt u dat slechte bestanden uw productiepijplijn verstoppen, wat zowel tijd als renderingbronnen bespaart.
Opmerking: Geavanceerde PDF-parsing en preflight-mogelijkheden worden volledig ondersteund door de PDFium Component.