Tekninen artikkeli

Automaattinen PDF-tarkistus (Preflight) ja riskiarviointi PDFiumin avulla

PDF, joka saapuu tuotantorajalle — tulostusjonoon, arkistoon, asiakkaan latausportaaliin — tulisi tarkastaa (audited) ennen kuin mikään renderöi sitä. Tiedosto saattaa kantaa mukanaan Launch-toimintoa (Launch action), joka on kytketty käynnistämään (start) ulkoinen ohjelma, tulostukseen liian karkeita (coarse) kuvia, salaus-sanakirjaa (encryption dictionary), joka kieltää juuri sen tulostustyön, jota varten se lähetettiin, tai PDF/A-tunnusta (label), jota se ei täytä. Tiedoston tarkastamista tällaisia sääntöjä vastaan ennen kuin se siirtyy työnkulkuun (workflow), kutsutaan ennakkotarkastukseksi (preflighting), ja PDFium C API antaa Delphille kaiken tarvittavan näiden tarkistusten toteuttamiseen suoraan, ilman yhdenkään sivun renderöintiä

Tämä artikkeli rakentaa itse tarkistukset: neljä auditointiluokkaa, joista jokainen on pieni rutiini, joka liittää löydöksiä (findings) jaettuun tuloslistaan (result list). Interaktiiviset elementit, resurssimittarit, suojaustila (security state) ja standardien merkinnät (standards markers) saavat kaikki toimivan koodin, mukaan lukien laskutoimitukset. Jos tarvitset koneistoa näiden tarkistusten ympärille — eräkansioiden silmukoita (batch folder loops), JSON- ja HTML-raporttitiedostoja, tiedostokohtaista eristystä (per-file isolation) — PDFium Component toimittaa valmiin (ready-made) preflight-moottorin, ja eräajojen ennakkotarkastuksen (batch preflight) CLI-artikkeli kattaa tämän putkituksen (plumbing). Nämä kaksi jakavat tarkoituksella saman poistumiskoodin sanaston (exit-code vocabulary), joten tässä kirjoitettu auditoija asettuu suoraan kyseisen eräajurin (batch driver) alle

Löydöstietue (finding record) ja poistumiskoodisopimus (exit-code contract)

Jokainen tarkistus kirjoittaa yhteen litteään (flat) tietuetyyppiin (record type), koska vaihtoehtoa, jossa jokainen tarkistus tulostaa oman tekstinsä (prose), ei voida myöhemmin laskea, suodattaa (filtered) tai kynnystää (thresholded). Neljä kenttää riittää

uses
  System.SysUtils, System.Math, System.IOUtils,
  System.Generics.Collections, pdfium_lib;

type
  TFindingSeverity = (fsInfo, fsWarning, fsError);

  TPreflightFinding = record
    Severity: TFindingSeverity;
    Code: string;       // vakaa koneavain (stable machine key), esim. 'ACT-LAUNCH'
    Page: Integer;      // 1-pohjainen; 0 tarkoittaa asiakirjatasoa (document level)
    Message: string;    // ihmisille; vapaa muotoiltavaksi julkaisujen välillä
  end;

  TFindings = TList<TPreflightFinding>;

procedure Add(Findings: TFindings; Severity: TFindingSeverity;
  const Code: string; Page: Integer; const Msg: string);
var
  F: TPreflightFinding;
begin
  F.Severity := Severity;
  F.Code := Code;
  F.Page := Page;
  F.Message := Msg;
  Findings.Add(F);
end;

Alajuoksun työkalut (downstream tooling) kytkeytyvät (keys) Code-kenttään, eivät koskaan Message-tekstiin, joka voi muuttua vapaasti. Prosessin poistumiskoodi (exit code) noudattaa samaa kolmen arvon (three-value) sopimusta kuin eräajoartikkeli (batch article): 0 tarkoittaa, ettei tiedosto tuottanut löydöksiä, 1 tarkoittaa, että löydöksiä on olemassa, ja 2 tarkoittaa, että itse auditointia (audit itself) ei voitu suorittaa, koska tiedostoa ei pystytty jäsentämään (failed to parse) tai se vaatii salasanan. Koodin 2 erillään pitäminen on tärkeää. Kansio täynnä korruptoituneita skannauksia tarkoittaa rikkinäistä skanneria alkuvirrassa (upstream), ei äkillistä vaatimustenmukaisuuden (compliance) romahdusta, ja näiden kahden yhdistäminen lähettää jonkun jahtaamaan (chasing) väärää ongelmaa

Interaktiiviset elementit: skriptit, launch-kohteet, ulkoiset linkit

PDFium luokittelee jokaisen löytämänsä toiminnon (action) kokonaislukutyypiksi (integer type), ja vakiot tiedostosta fpdf_doc.h kannattaa kiinnittää tarkasti, koska väärin kopioidut arvot sokeuttavat (blind) skannerin hiljaisesti. Todellinen (real) luettelointi (enumeration) on PDFACTION_UNSUPPORTED = 0, PDFACTION_GOTO = 1, PDFACTION_REMOTEGOTO = 2, PDFACTION_URI = 3, PDFACTION_LAUNCH = 4 ja PDFACTION_EMBEDDEDGOTO = 5. Huomioi mikä puuttuu: JavaScript-jäsentä (member) ei ole. Asiakirjatason (Document-level) skriptit eivät ole linkkitoimintoja eivätkä koskaan näy (show up) FPDFAction_GetType:n kautta; ne luetellaan (enumerated) erillisen kutsuperheen avulla. Auditoija, joka testaa toimintotyyppejä kuviteltua (imagined) JavaScript-vakiota vastaan, kääntyy, suorittaa (runs) eikä löydä koskaan mitään

const
  PDFACTION_GOTO         = 1;   // asiakirjan sisäinen hyppy: harmiton (harmless)
  PDFACTION_REMOTEGOTO   = 2;   // hyppy toiseen paikalliseen (local) tiedostoon
  PDFACTION_URI          = 3;   // avaa ulkoisen URL-osoitteen
  PDFACTION_LAUNCH       = 4;   // käynnistää (starts) ulkoisen ohjelman
  PDFACTION_EMBEDDEDGOTO = 5;   // hyppy upotettuun (embedded) tiedostoon

function ActionTarget(Doc: FPDF_DOCUMENT; Action: FPDF_ACTION;
  AType: ULONG): string;
var
  Buf: array[0..2047] of AnsiChar;
begin
  FillChar(Buf, SizeOf(Buf), 0);
  if AType = PDFACTION_URI then
    FPDFAction_GetURIPath(Doc, Action, @Buf, SizeOf(Buf))
  else
    FPDFAction_GetFilePath(Action, @Buf, SizeOf(Buf));
  Result := string(UTF8String(PAnsiChar(@Buf)));
end;

procedure AuditPageActions(Doc: FPDF_DOCUMENT; Page: FPDF_PAGE;
  PageNo: Integer; Findings: TFindings);
var
  StartPos: Integer;
  Link: FPDF_LINK;
  Action: FPDF_ACTION;
  AType: ULONG;
begin
  StartPos := 0;
  while FPDFLink_Enumerate(Page, @StartPos, @Link) <> 0 do
  begin
    Action := FPDFLink_GetAction(Link);
    if Action = nil then
      Continue;                 // vain kohde -linkki, ei liputettavaa (nothing to flag)
    AType := FPDFAction_GetType(Action);
    case AType of
      PDFACTION_LAUNCH:
        Add(Findings, fsError, 'ACT-LAUNCH', PageNo,
          'Launch-toiminnon kohde on "' + ActionTarget(Doc, Action, AType) + '"');
      PDFACTION_URI:
        Add(Findings, fsWarning, 'ACT-URI', PageNo,
          'linkki avaa kohteen ' + ActionTarget(Doc, Action, AType));
      PDFACTION_REMOTEGOTO, PDFACTION_EMBEDDEDGOTO:
        Add(Findings, fsWarning, 'ACT-XFILE', PageNo,
          'tiedoston välinen kohde (cross-file destination) "' + ActionTarget(Doc, Action, AType) + '"');
    end;                        // PDFACTION_GOTO pysyy hiljaa (silent) suunnitellusti
  end;
end;

procedure AuditDocumentBehaviors(Doc: FPDF_DOCUMENT; Findings: TFindings);
var
  N: Integer;
begin
  N := FPDFDoc_GetJavaScriptActionCount(Doc);
  if N > 0 then
    Add(Findings, fsError, 'JS-DOC', 0,
      Format('%d asiakirjatason JavaScript-toiminto(a) suoritetaan (run) avaamisen yhteydessä', [N]));
  N := FPDFDoc_GetAttachmentCount(Doc);
  if N > 0 then
    Add(Findings, fsWarning, 'ATT-EMB', 0,
      Format('%d upotettua liitetiedostoa', [N]));
end;

Vakavuusjako (severity split) koodaa politiikan (policy). Launch-toiminto (Launch action) on virhe (error), koska mielivaltaisen ohjelman käynnistäminen (starting an arbitrary program) on vaarallisinta, mitä PDF-tiedostossa napsauttaminen voi tehdä, eikä mikään lasku tarvitse sitä. Ulkoiset URL-osoitteet (URIs) ovat varoituksia: yleisiä laillisissa (legitimate) asiakirjoissa, mutta arvioijan (reviewer) pitäisi nähdä kohde napsauttamatta, koska näkyvän linkkitekstin ja todellisen kohteen (destination) ei tarvitse täsmätä. Asiakirjan sisäiset GoTo-hypyt ovat rakennetta, eivät käyttäytymistä, ja pysyvät raportin (report) ulkopuolella kokonaan — ennakkotarkastus, joka huutaa sutta jokaisen sisällysluettelon kohdalla, opettaa (trains) ihmisiä jättämään sen huomiotta. Skriptien runkojen (script bodies) lukemiseksi JavaScript-laskurin (count) takaa sekä allekirjoituksen MDP-tasojen ja XFA:n havaitsemiseksi, turvallisuusriskien arviointia koskeva artikkeli käy läpi saman pinnan (surface) komponentin oliokääreen (object wrapper) kautta

Resurssimittarit: efektiivinen (effective) kuvan DPI

PDF:n sisällä olevalla kuvalla ei ole omaa DPI:tä (DPI of its own). Sillä on pikseleitä (pixels), ja sivu (page) asettaa (places) nuo pikselit suorakulmioon, joka on mitattu pisteinä (points), joissa 72 pistettä tekee tuuman (inch). Resoluutio (Resolution) on olemassa vain näiden kahden suhteena (ratio), minkä vuoksi sama 600 kertaa 400 -kuva on terävä (razor sharp) pienoiskuvana (thumbnail) ja suttuinen (blurry mess) koko sivun kokoiseksi suurennettuna (full-page hero). Tarkastus tarvitsee siksi molemmat luvut jokaiselle kuvalle: lähteen pikselimitat (source pixel dimensions) kuvan metatiedoista ja sijoitetun suorakulmion (placed rectangle) objektin rajoista (object bounds)

procedure AuditPageImages(Page: FPDF_PAGE; PageNo: Integer;
  Findings: TFindings);
var
  I, ObjCount: Integer;
  Obj: FPDF_PAGEOBJECT;
  Meta: FPDF_IMAGEOBJ_METADATA;
  L, B, R, T: Single;
  WidthPt, HeightPt, DpiX, DpiY, EffDpi: Double;
begin
  ObjCount := FPDFPage_CountObjects(Page);
  for I := 0 to ObjCount - 1 do
  begin
    Obj := FPDFPage_GetObject(Page, I);
    if FPDFPageObj_GetType(Obj) <> FPDF_PAGEOBJ_IMAGE then
      Continue;
    if FPDFImageObj_GetImageMetadata(Obj, Page, @Meta) = 0 then
      Continue;
    if FPDFPageObj_GetBounds(Obj, @L, @B, @R, @T) = 0 then
      Continue;

    WidthPt  := R - L;              // sijoitettu koko (placed size) sivulla, pisteinä
    HeightPt := T - B;
    if (WidthPt <= 0) or (HeightPt <= 0) or
       (Meta.Width = 0) or (Meta.Height = 0) then
      Continue;

    // 72 pistettä = 1 tuuma, joten sijoitetut tuumat = pisteet / 72, ja
    // efektiivinen DPI = lähdepikselit / sijoitetut tuumat.
    DpiX := Meta.Width  / (WidthPt  / 72.0);
    DpiY := Meta.Height / (HeightPt / 72.0);
    EffDpi := Min(DpiX, DpiY);      // huonompi (worse) akseli ratkaisee tulostuslaadun (print quality)

    if EffDpi < 150.0 then
      Add(Findings, fsWarning, 'IMG-LOWRES', PageNo,
        Format('kuva %dx%d px sijoitettu koossa %.1fx%.1f pt = %.0f DPI efektiivinen',
          [Meta.Width, Meta.Height, WidthPt, HeightPt, EffDpi]))
    else if EffDpi > 600.0 then
      Add(Findings, fsInfo, 'IMG-BLOAT', PageNo,
        Format('kuva on %.0f DPI sijoitetussa koossa; uudelleennäytteistys (resampling) pienentäisi ' +
          'tiedostoa ilman näkyvää häviötä', [EffDpi]));
  end;
end;

Kynnykset (thresholds) ovat politiikkaa (policy), eivät fysiikkaa: 150 DPI on lattia (floor), jonka alapuolella toimistotulostus (office printing) pikselöityy näkyvästi, 300 on tavallinen kaupallinen tavoite (commercial target), ja kaikki yli 600:n ei tuota (buys no) näkyvää laatua (visible quality), mutta paisuttaa (inflates) tiedostokokoa, minkä vuoksi se raportoidaan (reports) informatiivisena paisumisena (informational bloat) eikä vikana (defect). Yksi rehellinen varoitus (honest caveat): FPDFPageObj_GetBounds palauttaa akseleiden suuntaisen (axis-aligned) laatikon, joten kierrolla (with rotation) sijoitetulle kuvalle laskettu luku (computed figure) aliarvioi todellisen tiheyden (true density). FPDF_IMAGEOBJ_METADATA -tietue (struct) sisältää myös horizontal_dpi ja vertical_dpi -kentät, jotka PDFium johtaa (derives) koko muunnosmatriisista (full transform matrix), ja näiden kahden tuloksen vertailu on halpa tapa havaita (spot) käännetyt (rotated) sijoitukset (placements). Sama pisteistä-pikseleiksi-matematiikka ohjaa (drives) renderöintiä vastakkaiseen suuntaan (opposite direction), jota käsitellään JPEG-vientiartikkelissa

Suojaustila: salaus (encryption) ja käyttöoikeusbitit (permission bits)

PDF-salaus määrittelee kaksi salasanaa (passwords), joilla on eri tehtävät (jobs). Käyttäjän salasana (user password) portittaa (gates) salauksen purun (decryption): ilman sitä tiedosto ei avaudu lainkaan (at all), ja FPDF_LoadDocument palauttaa nil-arvon ja FPDF_GetLastError raportoi FPDF_ERR_PASSWORD. Omistajan salasana (owner password) portittaa käyttöoikeudet (permissions): tiedosto, jota suojaa (protected) vain omistajan salasana, avautuu ilman tunnistetietoja (credentials), mutta kantaa rajoitusbittejä (restriction bits), joita vaatimustenmukaisen (conforming) lukijan on noudatettava (honor). Itse latausyritys (load attempt) on siksi ensimmäinen (first) turvallisuuskoetin (security probe), ja tämä ero ratkaisee (decides) poistumiskoodin (exit code) — käyttäjän salasanalla suojattua tiedostoa ei voi tarkastaa (unauditable) (koodi 2), kun taas omistajan salasanalla suojattu tiedosto tarkastetaan normaalisti (audits normally) ja se vain kerää löydöksiä (accumulates findings)

const
  FPDF_ERR_PASSWORD = 4;

function AuditSecurity(const FileName: string;
  Findings: TFindings): FPDF_DOCUMENT;
var
  Perms: ULONG;
  Revision: Integer;
begin
  Result := FPDF_LoadDocument(PAnsiChar(AnsiString(FileName)), nil);
  if Result = nil then
  begin
    if FPDF_GetLastError() = FPDF_ERR_PASSWORD then
      Add(Findings, fsError, 'SEC-USERPW', 0,
        'käyttäjän (avaus-)salasana (user password) vaaditaan; auditointi (audit) ei voi edetä')
    else
      Add(Findings, fsError, 'DOC-BROKEN', 0, 'tiedoston jäsennys epäonnistui');
    Exit;
  end;

  Revision := FPDF_GetSecurityHandlerRevision(Result);
  if Revision >= 0 then       // -1 tarkoittaa, että tiedostoa ei ole salattu
  begin
    // Avattu tyhjällä (empty) salasanalla, mutta salattu: vain omistajan salasana (owner-password-only).
    // Kuka tahansa voi lukea sen, mutta käyttöoikeusbitit (permission bits) rajoittavat sitä, mitä
    // vaatimustenmukainen lukija antaa heidän tehdä. Salaamattomat (Unencrypted) tiedostot raportoivat
    // kaikkien bittien olevan asetettuja, minkä vuoksi revision-portti (revision gate) on ensimmäisenä.
    Perms := FPDF_GetDocPermissions(Result);
    Add(Findings, fsInfo, 'SEC-ENC', 0,
      Format('salattu (encrypted), suojauksen käsittelijän (security handler) versio %d', [Revision]));
    if (Perms and 4) = 0 then      // bitti 3: tulostus (print)
      Add(Findings, fsWarning, 'SEC-NOPRINT', 0,
        'tulostusta (printing) ei ole sallittu');
    if (Perms and 16) = 0 then     // bitti 5: kopiointi / sisällön louhinta (extract content)
      Add(Findings, fsInfo, 'SEC-NOCOPY', 0,
        'sisällön louhintaa (content extraction) ei ole sallittu');
    if (Perms and 2048) = 0 then   // bitti 12: korkearesoluutioinen tulostus (high-resolution print)
      Add(Findings, fsWarning, 'SEC-LOWPRINT', 0,
        'vain matalaresoluutioinen tulostus (low-resolution printing) on sallittu');
  end;
end;

Maskit tulevat ISO 32000-1 -standardin taulukosta 22 (Table 22), joka numeroi bitit 1:stä alkaen: /P-arvon bitti 3 on maski 4, bitti 5 on 16, bitti 12 on 2048. Se, onko tietyllä löydöksellä merkitystä (matters), on reitityspäätös (routing decision). Tulostuspalvelun (print bureau) pitäisi hylätä (bounce) SEC-NOPRINT-tiedosto vastaanotossa (intake), jossa lähettäjä saa selvän viestin, eikä RIPissä (RIP) kolme tuntia ennen määräaikaa (deadline). Arkiston tulisi käsitellä (treat) itse SEC-ENC-löydöstä esteenä (blocker), sillä salaus ja pitkäaikainen säilytys eivät sovi yhteen (do not mix) — asia, jonka standarditarkistus on tekemässä (make) muodollisesti seuraavaksi

Standardien merkinnät (Standards markers): PDF/A-väitteen (claim) lukeminen

Tiedosto ilmoittaa (declares) PDF/A-vaatimustenmukaisuuden (conformance) XMP-metatietopaketissaan pdfaid:part -ominaisuuden (property) (1–4) ja pdfaid:conformance -ominaisuuden (tason kirjain (level letter), kuten b visuaaliselle uskollisuudelle (visual fidelity) tai a täydelle rakenteelliselle merkitsemiselle (structural tagging)) kautta. PDFiumin C API ei tarjoa XMP-lukijaa (accessor); FPDF_GetMetaText lukee vain Info-sanakirjan (dictionary), jossa tunniste (identification) ei sijaitse. Pelastustie (escape hatch) on itse (itself) standardin sääntö: ISO 19005 vaatii (requires), että XMP-metatietovirta tallennetaan pakkaamattomana (uncompressed), juuri (precisely) siksi, että työkalut voivat löytää sen ilman täyttä PDF-jäsentäjää (parser). Raaka (raw) tavuskannaus on siis laillinen väitteen ilmaisija (claim detector) — ja tiedosto, jonka väite piiloutuu pakatun virran (compressed stream) sisään, on jo rikkonut standardia, jota se väittää noudattavansa (claims)

function PdfAClaim(const FileName: string): string;
var
  Bytes: TBytes;
  S: RawByteString;
  P, Limit: Integer;
begin
  Result := '';                     // tyhjä = ei PDF/A-väitettä (claim present)
  Bytes := TFile.ReadAllBytes(FileName);
  if Length(Bytes) = 0 then
    Exit;
  SetString(S, PAnsiChar(@Bytes[0]), Length(Bytes));
  P := Pos('pdfaid:part', S);       // XMP-tunnisteskeema (identification schema)
  if P = 0 then
    Exit;
  // Käsittelee (Handles) sekä <pdfaid:part>2</pdfaid:part> että pdfaid:part="2":
  // ota ensimmäinen (first) numero ominaisuuden (property) nimen jälkeen.
  Limit := Min(P + 32, Length(S));
  Inc(P, Length('pdfaid:part'));
  while (P <= Limit) and not (S[P] in ['1'..'4']) do
    Inc(P);
  if P <= Limit then
    Result := 'PDF/A-' + Char(S[P]);
end;

Tämän tuottama löydös (finding) on tarkoituksella informatiivinen (informational), koska väite on ilmoitus (declaration), ei tiedoston ominaisuus (property). XMP-merkintä on yksi rivi XML:ää, jonka kuka tahansa tuottaja voi kirjoittaa, mukaan lukien (including) rikkinäinen (broken); vaatimustenmukaisuus (conformance) tarkoittaa, että tiedosto todella täyttää sadat (hundreds) säännöt upotetuista fonteista (embedded fonts), laitteistoriippumattomasta väristä (device-independent color) ja kielletyistä ominaisuuksista. Väitteen havaitseminen kertoo (tells) vain, mitkä tiedostot tulee reitittää (route) todelliseen (real) validointiin, ei mitään muuta. Komponentin sisäänrakennettu (built-in) preflight-moottori (engine) suorittaa tuon validoinnin (validation) poikki PDF/A-, PDF/UA- ja PDF/X-profiilien, ja eräajon CLI-artikkeli (batch CLI article) näyttää, kuinka se kytketään (wire) putkeen raportteineen, jotka tarkastaja voi avata myöhemmin

Suoritus (run) ongelmatiedostoa vastaan

Ajuri (driver) yhdistää (strings together) tarkistukset: suojaus (security) ensin, koska se päättää, suoritetaanko auditointi ollenkaan (at all), sitten asiakirjatason käyttäytyminen ja standardiväite, minkä jälkeen käydään läpi sivu-silmukka (page loop) toimintoja (actions) ja kuvia (images) varten

function AuditFile(const FileName: string; Findings: TFindings): Integer;
var
  Doc: FPDF_DOCUMENT;
  Page: FPDF_PAGE;
  I: Integer;
  Claim: string;
begin
  Doc := AuditSecurity(FileName, Findings);
  if Doc = nil then
    Exit(2);                        // tarkastus (audit) epäonnistui, ei tuomio (verdict)
  try
    AuditDocumentBehaviors(Doc, Findings);
    Claim := PdfAClaim(FileName);
    if Claim <> '' then
      Add(Findings, fsInfo, 'STD-PDFA', 0,
        Claim + ' vaatimustenmukaisuus ilmoitettu (vain ilmoitus, ei validoitu)');
    for I := 0 to FPDF_GetPageCount(Doc) - 1 do
    begin
      Page := FPDF_LoadPage(Doc, I);
      if Page = nil then
      begin
        Add(Findings, fsError, 'PAGE-BROKEN', I + 1, 'sivun jäsennys (parse) epäonnistui');
        Continue;
      end;
      try
        AuditPageActions(Doc, Page, I + 1, Findings);
        AuditPageImages(Page, I + 1, Findings);
      finally
        FPDF_ClosePage(Page);
      end;
    end;
  finally
    FPDF_CloseDocument(Doc);
  end;
  if Findings.Count > 0 then
    Result := 1
  else
    Result := 0;
end;

Esitteeseen (brochure), joka palasi ulkopuoliselta toimistolta (outside agency), ajetun testin tuloste (output) näyttää tältä:

> preflight_audit brochure_final.pdf
brochure_final.pdf: 5 löydöstä (finding(s))
  [ERROR]   ACT-LAUNCH   page 3   Launch-toiminto (Launch action) kohteena "..\tools\setup.exe"
  [ERROR]   JS-DOC       doc      2 asiakirjatason (document-level) JavaScript-toimintoa (action) suoritetaan avaamisen yhteydessä
  [WARNING] IMG-LOWRES   page 7   kuva 412x287 px sijoitettu koossa 396.0x275.8 pt = 75 DPI efektiivinen (effective)
  [WARNING] SEC-NOPRINT  doc      tulostusta (printing) ei ole sallittu
  [INFO]    STD-PDFA     doc      PDF/A-2 vaatimustenmukaisuus ilmoitettu (vain ilmoitus, ei validoitu)
exit code 1

Jokainen rivi vaatii toimia (actionable) yksinään, mutta yhdistelmä on varsinainen tuomio (verdict). Tämä tiedosto väittää olevansa PDF/A-2, mutta kantaa mukanaan salaus-sanakirjaa (encryption dictionary) ja elävää (live) JavaScriptiä, ja PDF/A kieltää (forbids) molemmat suoraan (outright) — joten väite on todistettavasti valheellinen (provably false) ennen kuin yksikään syvempi validaattori (deep validator) on käynnistynyt. Juuri tällaisen (that is the kind of) ristiriidan litteä löydöslista nostaa esiin, ja jonka booleanin pass/fail piilottaa (hides)

Mitä tämä auditointi ei voi kertoa sinulle

Rehellisyys (Honesty) laajuudesta (scope) on se, mikä pitää (keeps) preflight-työkalun luotettavana (trusted). Kaikki yllä oleva lukee sitä, mitä tiedosto ilmoittaa (declares) itsestään: PDFium jäsentää rakenteen, ja tämä auditointi luetteloi sen (inventories it). Se ei suorita PDF/A-validointia — ei glyyfi-peitto (glyph-coverage) -tarkistuksia upotetuille fonteille, ei väriavaruuden analyysia tulostusintentioita (output intents) vastaan, ei mitään klausuulitason (clause-level) sääntöjä, jotka erottavat väitteen vaatimustenmukaisuudesta (conformance); siihen tarvitset omistetun validaattorin, kuten komponentin preflight-moottorin tai veraPDF:n. Käyttöoikeusbitit ovat ilmoituksia (declarations), joita vaatimustenmukaiset lukijat noudattavat (honor), eivät kryptografisia muureja (walls), joten SEC-NOPRINT kuvaa aikomusta (intent) pikemminkin kuin sen täytäntöönpanoa (enforcement). Toimintojen skannaus (action scan) kattaa linkkihuomautukset (link annotations) ja asiakirjatason skriptit; lomakekenttien (form-field) tapahtumasanakirjoihin (event dictionaries) haudatut skriptit tarvitsevat (need) lomakkeiden ohjelmointirajapinnat (form APIs) päällensä. Ja jos laajennat auditointia allekirjoituksen (signature) tarkistuksella, se raportoi ilmoitetun aikomuksen (declared intent), ei vahvistettua (verified) kryptografiaa — varmennetun (certificate) ketjun (chain) validointi on erillinen tehtävä (job). Preflight-auditointi on sisääntulohaastattelu (intake interview), ei oikeudenkäynti (trial): sen tehtävänä on tehdä reitityspäätöksestä (routing decision) tietoon perustuva, nopea ja toistettava

Huomautus: Koko tässä auditoinnissa käytetyt asiakirja-, sivu-, huomautus- ja kuvaobjektien (document, page, annotation, and image object) ohjelmointirajapinnat (APIs), yhdessä korkean tason Delphi-kääreen (wrapper) ja täyden standardivalidointia suorittavan preflight-moottorin (standards-validation preflight engine) kanssa toimitetaan (ship) PDFium Componentin mukana