Tehnični članak

Preflight različice PDF v Delphiju: pravila RL in GEO

PDFlibPas (PDF Library for Delphi) preveri vsak objekt proti tabeli pravil različic PDF, preden zapiše datoteko, preflight različice PDF pa je do nedavna navadne merilne slovarje CAD zamenjal za geoprostorske. Ena stransko CAD risba se je lepo naložila, nato je SaveToFile vrnil 0 s LastErrorCode 602 in zahteval 1.7 ExtensionLevel 3. Popravljena pravila obravnavajo pravokotne slovarje /Measure (/Subtype /RL) kot navaden PDF 1.6 in vrzel razširitve pustijo za resnične geoprostorske označevalce

Datoteka je prišla skozi sprejem korpusa: ena stran, ena skupina izbirne vsebine, dva pravokotna merilna vidika, taka vrsta izhoda, kot jo zapiše arhitekturni paket CAD, da lahko pregledovalnik bere razdalje s tlorisa. Nič na njej ni bilo eksotičnega, točno zato je bila zavrnitev pomembna. Preflight, ki blokira veljavno datoteko, je hujši od počasnega, ker klicatelj dobi diagnostiko, ki izgleda avtoritativno, in kaže na zmožnost, ki je dokument ne vsebuje. Popravek je vzel dva dela: branje specifikacije za enim pravilom in uvid, da pravilo na ravni, na kateri je gledalo, ni moglo ločiti dveh vrst slovarjev

Kako deluje preflight različice PDFlibPas ob shranjevanju?

Zapora shranjevanja, PrepareAndCheckSaveVersion, primerja vsak posredni objekt z PDFFeatureRules in odpove pri prvem pravilu, ki se hkrati ujema in potrebuje več, kot cilj dovoljuje. Cilj je različica dokumenta (ali različica, pripeta s LockSaveVersion) plus raven razširitve Adobe, deklarirana pod /Extensions /ADBE. Vsak zapis TPDFFeatureRule nosi MinVersion, MinExtensionLevel, MatchKind, kot je fmkDictKey ali fmkDictSubtype, niz Match, človeku berljivo ime Feature in neobvezen povratni klic. AddRule registrira navadno pravilo različice; AddExtensionRule vselej pripne MinVersion na 17 in dodane še raven razširitve, zato pravilu razširitve lahko ustreza le PDF 1.7 plus pravi vnos /Extensions. Ko se zapora sproži, zahtevana različica in ime zmožnosti ostanejo za klicatelja, ključi GetInformation 311, 312 in 313 pa jih izpostavijo

Preflight različice ob shranjevanju v PDFlibPas: PrepareAndCheckSaveVersion primerja vsak objekt z zapisi PDFFeatureRules, ki nosijo MinVersion, raven razširitve in vrsto ujemanja, AddExtensionRule pripne zahtevo na PDF 1.7 plus raven razširitve, prvo ujemanje, ki mu cilj ne more ustreči, pa ustavi shranjevanje z napako 602
Ključi GetInformation 311, 312 in 313 spremenijo zavrnitev v diagnozo, sporočijo zahtevano različico, zmožnost, ki jo je sprožila, in zaklenjen cilj, zato lahko klicatelj popravi datoteko ali pravilo, namesto da bi ugibal
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.LoadFromFile('floor-plan.pdf', '') <> 1 then
      raise Exception.Create('load failed');
    if Pdf.SaveToFile('floor-plan-out.pdf') <> 1 then
      if Pdf.LastErrorCode = PDFLIB_ERROR_VERSION_COMPLIANCE then
        // 311: zahtevana različica, 312: zmožnost, ki jo je sprožila,
        // 313: različica, na katero je cilj shranjevanja zaklenjen ('', ko ni zaklenjen)
        Writeln('Needs ', Pdf.GetInformation(311),
          ' for ', Pdf.GetInformation(312),
          ', locked at [', Pdf.GetInformation(313), ']');
  finally
    Pdf.Free;
  end;
end;

Zakaj je navadna CAD risba odpovedala z napako 602?

Tabela pravil je vsebovala AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), ki se je sprožila na vsakem slovarju, ki je zgolj imel ključ /Measure, vsak merilni vidik pa ga ima. Polje /VP strani nosi slovarje vidikov, vsak vidik kaže na svoj merilni slovar prek /Measure, ujemanje po navzočnosti ključa pa se je tam ustavilo, brez pogleda, kaj merilni slovar dejansko je. Sken zmožnosti ob nalaganju je lahko dvignil številko različice dokumenta na 1.7, deklaracije /Extensions pa nikoli ne zapiše v imenu vhodne datoteke, zato je zapora shranjevanja videla PDF 1.7 na ravni razširitve 0 in sporočila 1.7 ExtensionLevel 3. Ta odklonitev izmišljanja deklaracije razširitve je namerna: knjižnica vhodne datoteke ne povzdigne tiho, da bi prekrila napačno pravilo

Specifikacija je pri pravokotnem primeru nedvoumna. Slovarji Measure so prišli v PDF 1.6, ISO 32000-1 §12.9 pa daje /Subtype privzeto RL, pravokotni koordinatni sistem, opisan z lastnim naborom vnosov: merilo, oblike števil X in Y, razdalja in površina. Geoprostorsko merjenje je poznejši dodatek iz Adobe Extension Level 3 na vrhu PDF 1.7, prepoznaven po /Subtype /GEO, ki nosi geografska polja točk, slovarje koordinatnih sistemov in prikazne enote, strukture, prehojene v branju vidikov GeoPDF ter polj GPTS in LPTS v Delphiju. Oba slovarja visita na istem ključu /Measure, zato pravilo, ki se ustavi pri ključu, ne more biti prav za oba. Ločevalna informacija sedi eno raven nižje, v samem merilnem slovarju

En ključ /Measure, dva slovarja v PDFlibPas: pravokotno merjenje, z /RL ali izpuščenim podtipom, potrebuje le PDF 1.6, geoprostorski slovar pa 1.7 ExtensionLevel 3, zato CB_GeospatialDictionary odloča po vsebini, kjer stara pravilo po ključu ni znala ločiti
Preflight, ki blokira veljavno datoteko, je hujši od počasnega, ker klicatelj dobi avtoritativno diagnostiko o zmožnosti, ki je dokument nikoli ni vseboval, zato se je ločevalni preizkus preselil eno raven nižje

Kaj popravljen nabor pravil še vedno uveljavlja?

Popravek izbriše brezpogojno pravilo po ključu in pusti zapore, ki opisujejo prave zahteve po različici. Stran, ki nosi /VP ali /UserUnit, še vedno potrebuje PDF 1.6 prek CB_PagePDF16Entries, ključ /PtData še vedno potrebuje raven razširitve 3, CB_GeospatialDictionary pa odloča, ali je merilni slovar geoprostorski, po njegovi vsebini in ne po ključu, po katerem je prišel

// Odstranjeno: vsak slovar s ključem /Measure je štel kot geoprostorski
// AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil);

AddRule(16, fmkCustom, '', 'Page PDF 1.6 entry /UserUnit /VP', CB_PagePDF16Entries);
AddExtensionRule(3, fmkDictKey, 'PtData', '/PtData geospatial dictionary', Nil);
AddExtensionRule(3, fmkCustom, '', 'geospatial measure dictionary', CB_GeospatialDictionary);

function CB_GeospatialDictionary(Obj: TPDFObject; const Ctx: TPDFRuleContext): Boolean;
var
  Dict: TPDFDictionary;
begin
  Result := False;
  if not (Obj is TPDFDictionary) then
    Exit;
  Dict := TPDFDictionary(Obj);
  Result := (Dict.StringValue('Subtype') = 'GEO') or
    (Dict.FindIndexByKeyName('GCS') >= 0) or (Dict.FindIndexByKeyName('DCS') >= 0) or
    (Dict.FindIndexByKeyName('GPTS') >= 0) or (Dict.FindIndexByKeyName('LPTS') >= 0) or
    (Dict.FindIndexByKeyName('PDU') >= 0);
end;

Skupni regresiji Delphi in FPC pripneta to mejo z obeh strani. Vidik, katerega merilni slovar izpusti /Subtype, in enega, ki izreče /RL, oba uspejo pri PDF 1.6, ista stran je še vedno zavrnjena pri PDF 1.5, zaznavanje zmožnosti pa ne poroča več razširitve zanjo. Dodatek polja /GPTS obrne sodbo nazaj na 1.7 ExtensionLevel 3, kar uspe, ko je raven razširitve deklarirana, gol slovar /Subtype /GEO pa je brez nje zavrnjen. Povratni klic je po zasnovi konservativen: pravokotni slovar, ki nosi tudi odvečni ključ /GCS ali /PDU, se obravnava kot geoprostorski, saj ti ključi v modelu RL nimajo pomena

LockSaveVersion je mesto, kjer ta sprememba postane vidna klicateljem. TPDFlib.LockSaveVersion sprejme '1.0' do '1.7', za karkoli drugega vrne 0, pripne različico dokumenta in ustavi klice na strani zapisovalnika, da je tiho dvignejo, zapora shranjevanja pa vseeno teče proti zaklenjeni vrednosti. S popravljenimi pravili se datoteka CAD, zaklenjena na 1.6, shrani čisto. Pravi GeoPDF, zaklenjen na 1.6, še vedno dobi 602, kar je pravilen odgovor, geoprostorski klici za ustvarjanje, kot je SetMeasureDictCoordinateSystem, pa sami deklarirajo raven razširitve 3, ko to vsebino gradite prek API-ja

if Pdf.LockSaveVersion('1.6') <> 1 then
  raise Exception.Create('unsupported version string');
if Pdf.SaveToFile('floor-plan-16.pdf') <> 1 then
begin
  if Pdf.LastErrorCode = PDFLIB_ERROR_VERSION_COMPLIANCE then
    // Prava vsebina nad 1.6, na primer merilni slovar GEO
    raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
      [string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;

Zakaj je bil pregled po pravilih različic počasnejši, kot je moral biti?

Pregled je pred preizkusom kopiral vsak TPDFFeatureRule v lokalni zapis, ker zapis nosi dve polji AnsiString, pa je vsaka kopija prilagodila dva števca referenc in sprostila prejšnje vrednosti. Preflight obišče vsako vozlišče vsakega drevesa objektov, vključno s skalarji, zato se je ta strošek pomnožil število objektov krat število pravil, pravila, ki na ciljno različico sploh niso veljala, pa so bila najprej kopirana in šele potem preskočena. Ker se PDFFeatureRules napolni enkrat pri inicializaciji enote in obravnava kot samo za branje, v3.539.17 vnose tabele podaja naravnost MatchSingleRule in RuleExceedsTarget, katerih parametra const Rule vzameta referenco, brez da bi se dotaknila nizov

Pospešitev pregleda po pravilih v PDFlibPas: preflight je pred preizkusom kopiral vsak zapis TPDFFeatureRule, s prilagajanjem števcev referenc AnsiString za vsak obiskani objekt, parametri const pa zdaj berejo tabelo samo za branje na mestu, kar je mediano rundo ujemanja pravil skrajšalo iz 0.711 s na 0.203 s
Prirastek je resničen, a ozek: polno shranjevanje plačuje še odloženo zaznavanje zmožnosti, dekodiranje objektov in serializacijo, izmerjeno razmerje pa pripada poti ujemanja pravil in ne celotnemu času shranjevanja
// Prej: upravljana kopija zapisa na pravilo, na obiskani objekt
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
  Continue;

// Po: parametri const berejo nespremenljiv vnos tabele na mestu
if not RuleExceedsTarget(PDFFeatureRules[X], TargetVersion, TargetExtensionLevel) then
  Continue;
if MatchSingleRule(Obj, Ctx, PDFFeatureRules[X]) then
begin
  RequiredVersion := RequiredVersionString(PDFFeatureRules[X]);
  FeatureName := PDFFeatureRules[X].Feature;
  Result := False;
  Exit;
end;

Izmerjeni učinek je ozek in bi ga bilo treba citirati takole. Benchmark preizkusi polje 20.000 številskih objektov proti cilju PDF 1.4 desetkrat na rundo; zgrajen s FPC Win64 pri -O2, je mediana petih rund padla iz 0.711 s na 0.203 s, obratni vrstni red obeh gradenj pa je dal 0.459 s proti 0.150 s. To je približno 3-kratni prirastek na sami poti ujemanja pravil. Pravo shranjevanje plačuje še odloženo zaznavanje zmožnosti, dekodiranje objektov in serializacijo, zato se razmerje ne prenese na celotni čas shranjevanja. Vrstni red pravil, povratni klici, pragi različic in diagnostika prve odpovedi so nespremenjeni, nobeno pravilo pa ni bilo predpomnjeno čez shranjevanja ali preskočeno, da bi prišli do tega

Kaj preverite, ko naložen PDF odpove preflightu različice?

Preberite ključa 311 in 312, preden se lotite različice. Če zmožnost imenuje geoprostorski slovar in datoteka riše le pravokotna merjenja, je bil to ta lažno pozitiven primer, in trenutna gradnja datoteko shrani nespremenjeno. Če je zmožnost prava, bodisi deklarirajte razširitev bodisi zaklenite na različico, ki vsebino resnično vsebuje; dvig različice samo zato, da bi utišal zaporo, skrije vprašanje, ali lahko nadaljnji porabniki preberejo, kar dobavljate. Isto načelo omejenih, dokazov podprtih preizkusov poganja preflight avtorskega načina PDF/E-1 za inženirske dokumente, kjer risbe CAD srečajo standard skladnosti in ne številke različice

Preverjanja skladnosti različic, merilni in geoprostorski slovarji ter zaklepanje različice shranjevanja so vsi del PDF Library for Delphi, orodja PDFlibPas za razvijalce Delphi, C++Builder in Lazarus