Műszaki cikk

PDF verzió-preflight Delphiben: RL és GEO Measure szabályok

A PDFlibPas (PDF Library for Delphi) minden objektumot egy PDF verziószabály-táblával ellenőriz, mielőtt fájlt ír, és eddig ez a PDF verzió-preflight közönséges CAD mérési szótárakat kevert össze geospatialokkal. Egy egyoldalas CAD rajz szépen betöltött, majd a SaveToFile 0-t adott vissza LastErrorCode 602-vel, és 1.7 ExtensionLevel 3-at követelt. A javított szabályok a derékszögű /Measure szótárakat (/Subtype /RL) sima PDF 1.6-ként kezelik, és az extension kaput a valódi geospatial jelekre tartogatják

A fájl corpus-felvételen keresztül érkezett: egy oldal, egy optional-content csoport, két derékszögű mérési viewport, az a kimenet, amit egy építészeti CAD csomag ír, hogy a megjelenítő távolságokat olvashasson le egy alaprajzról. Semmi volt benne egzotikus, pontosan ezért számított az elutasítás. Egy preflight, ami blokkol egy érvényes fájlt, rosszabb egy lassúnál, mert a hívó egy hitelesnek tűnő diagnosztikát kap, ami egy olyan funkcióra mutat, amit a dokumentum nem tartalmaz. A javítás két részből állt: az egyik szabály mögötti specifikumolvasásból, és abból a felismerésből, hogy a szabály a vizsgálat szintjén nem tudta megkülönböztetni a két szótártípust

Hogyan működik a PDFlibPas mentésidőbeni verzió-preflightje?

A mentési kapu, a PrepareAndCheckSaveVersion, minden indirekt objektumot összevet a PDFFeatureRules-szal, és az első olyan szabálynál bukik meg, ami egyaránt illeszkedik és többet kíván, mint amennyit a cél megenged. A cél a dokumentum verziója (vagy a LockSaveVersion-nel rögzített verzió), plusz a /Extensions /ADBE alatt deklarált Adobe extension szint. Minden TPDFFeatureRule rekord hordoz egy MinVersion-t, egy MinExtensionLevel-t, egy MatchKind-ot, például fmkDictKey vagy fmkDictSubtype, egy Match stringet, ember által olvasható Feature nevet és opcionális callbacket. Az AddRule sima verziószabályt regisztrál; az AddExtensionRule mindig 17-en rögzíti a MinVersion-t, és Extension szintet rak rá, így egy extension szabályt csak PDF 1.7 és a megfelelő /Extensions bejegyzés együtt elégíthet ki. Amikor a kapu élesedik, a szükséges verzió és a funkció neve megmarad a hívónak, és a GetInformation 311-es, 312-es és 313-as kulcsai exponálják őket

Mentésidőbeni verzió-preflight a PDFlibPas-ben: a PrepareAndCheckSaveVersion minden objektumot összevet a PDFFeatureRules rekordokkal, amik MinVersion-t, extension szintet és illesztési fajtát hordoznak, az AddExtensionRule a követelményt PDF 1.7 plusz extension szintre rögzíti, és az első olyan találat, amit a cél nem tud kielégíteni, 602-es hibával megállítja a mentést
A GetInformation 311-es, 312-es és 313-as kulcsai az elutasításból diagnosztikát csinálnak, jelentve a szükséges verziót, a kiváltó funkciót és a rögzített célt, így a hívó kijavíthatja a fájlt vagy a szabályt a találgatás helyett
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: szükséges verzió, 312: a kiváltó funkció,
        // 313: a verzió, amire a mentési cél rögzítve van ('' ha nincs rögzítve)
        Writeln('Needs ', Pdf.GetInformation(311),
          ' for ', Pdf.GetInformation(312),
          ', locked at [', Pdf.GetInformation(313), ']');
  finally
    Pdf.Free;
  end;
end;

Miért bukott meg 602-es hibával egy sima CAD rajz?

A szabálytábla tartalmazta az AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil)-t, ami bármelyik szótáron élesedett, aminek csupán volt /Measure kulcsa — és minden mérési viewportnak van. Az oldal /VP tömbje viewport szótárakat tart, minden viewport a /Measure-ön keresztül mutat a saját measure szótárára, és a kulcsjelenlét-illesztés ennyi volt, anélkül, hogy megnézte volna, mi is az a measure szótár valójában. A betöltésidejű funkcióvizsgálat így felemelhette a dokumentum verziószámát 1.7-re, de soha nem ír /Extensions deklarációt egy bemeneti fájl nevében, így a mentési kapu PDF 1.7-t látott 0-s extension szinten, és 1.7 ExtensionLevel 3-at jelentett. Ez a vonakodás, hogy ne gyártson extension deklarációt, szándékos: a library nem lépteti csendben előre a bemeneti fájlt, hogy betapasoljon egy rossz szabályt

A specifikum a derékszögű esetről nem hagy kétséget. A Measure szótárak a PDF 1.6-ban érkeztek, az ISO 32000-1 §12.9-e pedig a /Subtype-nak RL alapértéket ad, egy derékszögű koordinátarendszert, amit a saját bejegyzéskészlete ír le: skálaarány, X és Y számformátumok, távolság és terület. A geospatial mérés a későbbi kiegészítés, az Adobe Extension Level 3 a PDF 1.7 tetején, /Subtype /GEO jelöli, és földrajzi ponttömböket, koordinátarendszer-szótárakat és megjelenítési egységeket hordoz — ezekről a struktúrákról szól a GeoPDF viewportok, GPTS és LPTS tömbök olvasása Delphiben. Mindkét szótár ugyanahhoz a /Measure kulcshoz akasztódik, így bármelyik szabály, ami a kulcsnál megáll, nem lehet egyszerre mindkettőre igaz. A megkülönböztető információ eggyel lejjebb ül, magában a measure szótárban

Egy /Measure kulcs, két szótár a PDFlibPas-ben: a derékszögű mérés, /RL-lel vagy a szubtípus elhagyásával, csak PDF 1.6-ot igényel, a geospatial szótár pedig 1.7 ExtensionLevel 3-at, így a CB_GeospatialDictionary tartalom alapján dönt ott, ahol a régi kulcsszabály nem tudta őket megkülönböztetni
Egy érvényes fájlt blokkoló preflight rosszabb egy lassúnál, mert a hívó egy hiteles diagnosztikát kap egy olyan funkcióról, amit a dokumentum soha nem tartalmazott, ezért költözött eggyel lejjebb a megkülönböztető ellenőrzés

Mit tart meg még mindig a javított szabálykészlet?

A javítás törli a feltétel nélküli kulcsszabályt, és meghagyja azokat a kapukat, amik valódi verziókövetelményeket írnak le. Egy /VP-t vagy /UserUnit-ot hordozó oldal továbbra is PDF 1.6-ot igényel a CB_PagePDF16Entries-en át, egy /PtData kulcs továbbra is 3-as extension szintet kíván, a CB_GeospatialDictionary pedig tartalma alapján dönti el, hogy egy measure szótár geospatial-e, nem az által a kulcs által, amin át megérkezett

// Eltávolítva: minden /Measure kulcsú szótár geospatialnak számított
// 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;

A közös Delphi és FPC regressziók mindkét oldalról rögzítik ezt a határt. Egy viewport, aminek a measure szótára elhagyja a /Subtype-ot, és egy, ami kiírja az /RL-t, egyaránt átment PDF 1.6-on, ugyanaz az oldal PDF 1.5-nél továbbra is elutasításra kerül, és a funkciófelismerés már nem jelent extensiont érte. Egy /GPTS tömb hozzáadása visszabillenti az ítéletet 1.7 ExtensionLevel 3-ra, ami átment, ha a kiterjesztési szint deklarálva van, és egy csupasz /Subtype /GEO szótár nélküle elutasításra kerül. A callback szándékosan konzervatív: egy derékszögű szótár, ami egy kóbor /GCS vagy /PDU kulcsot is hordoz, geospatialként kezelődik, mert ezek a kulcsok a RL modellben semmit sem jelentenek

A LockSaveVersion az a pont, ahol ez a változás láthatóvá válik a hívóknak. A TPDFlib.LockSaveVersion '1.0'-tól '1.7'-ig fogad el, bármire mással 0-t ad vissza, rögzíti a dokumentum verzióját, és megakadályozza, hogy az író oldali hívások csendben felemeljék, a mentési kapu mégis a rögzített értékhez mér. A javított szabályokkal egy 1.6-ra rögzített CAD fájl tisztán mentődik. Egy valódi GeoPDF, 1.6-ra rögzítve, továbbra is 602-t kap, ami a helyes válasz, és a geospatial szerzői hívások, mint a SetMeasureDictCoordinateSystem, maguk deklarálják a 3-as extension szintet, amikor azt a tartalmat az API-n keresztül építed

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
    // Valódi tartalom 1.6 fölött, például egy GEO measure szótár
    raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
      [string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;

Miért volt lassabb a verziószabály-vizsgálat a szükségesnél?

A vizsgálat minden TPDFFeatureRule-t helyi rekordba másolt, mielőtt tesztelte volna, és mivel a rekord két AnsiString mezőt hordoz, minden másolat két referenciaszámot állított és elengedte az előző értékeket. A preflight minden objektumfa minden csomópontját felkeresi, a skalárokat is beleértve, így ez a költség objektumszámot szoroz szabályszámmal, és a célverzióra egyáltalán nem vonatkozó szabályok előbb másolódtak, aztán ugrottak át. Mivel a PDFFeatureRules-t a unit inicializálásakor egyszer töltik fel, és csak olvasásra szánták, a v3.539.17 a táblabejegyzéseket egyenesen a MatchSingleRule-nek és a RuleExceedsTarget-nek adja át, amiknek a const Rule paraméterei referenciát vesznek a stringek megérintése nélkül

Szabályvizsgálat gyorsulás a PDFlibPas-ben: a preflight korábban minden TPDFFeatureRule rekordot lemásolt, mielőtt tesztelte, AnsiString referenciaszámokat állítva minden felkeresett objektumra, a const paraméterek most helyben olvassák a csak olvasható táblát, a szabályillesztési kör mediánját 0,711 s-ről 0,203 s-re vágva
A nyereség valódi, de szűk: egy teljes mentés a halasztott funkciófelismerésért, az objektumdekódolásért és a szerializálásért is fizet, így a mért hányados a szabályillesztési útvonalé, nem a teljes mentési időé
// Előtte: menedzselt rekordmásolat szabályonként, felkeresett objektumonként
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
  Continue;

// Utána: a const paraméterek helyben olvassák a megváltoztathatatlan táblabejegyzést
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;

A mért hatás szűk, és úgy is érdemes idézni. A benchmark egy 20 000 numerikus objektumból álló tömböt ellenőriz PDF 1.4-es célra körönként tízszer; FPC Win64-gyel fordítva -O2-n, öt kör mediánja 0,711 s-ről 0,203 s-re esett, és a két build fordított sorrendben futtatva 0,459 s-ot adott 0,150 s ellenében. Ez nagyjából 3-szoros nyereség a szabályillesztési útvonalon önmagában. Egy valódi mentés a halasztott funkciófelismerésért, az objektumdekódolásért és a szerializálásért is fizet, így a hányados nem vetül át a teljes mentési időre. A szabálysorrend, a callbackek, a verzióküszöbök és az első hiba diagnosztikája változatlan, és egyetlen szabály sem került cache-elésre mentések között, és semmi sem lett átugorva az eredményért

Mit érdemes ellenőrizned, amikor egy betöltött PDF megbukik a verzió-preflighten?

Olvasd ki a 311-es és 312-es kulcsokat, mielőtt a verzióhoz nyúlnál. Ha a funkció geospatial szótárt nevez meg, és a fájl csak derékszögű méréseket rajzol, az volt ez a hamis pozitív, és egy aktuális build változatlanul menti a fájlt. Ha a funkció valódi, vagy deklaráld a kiterjesztést, vagy rögzíts olyan verzióra, ami őszintén tartalmazza a tartalmat; a verzió felemelése csupán a kapu elhallgattatására elrejti a kérdést, hogy a downstream fogyasztók el tudják-e olvasni, amit szállítasz. Ugyanez a határolt, bizonyítékkal támasztott ellenőrzések elve hajtja a PDF/E-1 author-mode preflightet mérnöki dokumentumokhoz, ahol a CAD rajzok konformanciastandarddal találkoznak, nem verziószámmal

A verziómegfelelőségi ellenőrzések, a mérési és geospatial szótárak és a mentésiverzió-rögzítés mind a PDF Library for Delphi, a PDFlibPas eszközkészlet Delphi, C++Builder és Lazarus fejlesztőknek részei