PDFlibPas (PDF Library for Delphi) kontroluje každý objekt proti tabuľke pravidiel verzií PDF, skôr než zapíše súbor, a ešte donedávna si ten PDF version preflight plietol obyčajné CAD meracie slovníky s geopriestorovými. Jednostranová CAD kresba sa načítala pekne a potom SaveToFile vrátilo 0 s LastErrorCode 602 a žiadalo si 1.7 ExtensionLevel 3. Opravené pravidlá berú rectilinear slovníky /Measure (/Subtype /RL) ako obyčajné PDF 1.6 a nechávajú extenzijskú bránu skutočným geopriestorovým značkám
Súbor prišiel cez corpus admisiu: jedna strana, jedna skupina optional content, dva rectilinear meracie viewporty, typ výstupu, ktorý píše architektonický CAD balík, aby si prehliadač vedel prečítať vzdialenosti z pôdorysu. Nič na ňom nebolo exotické, a presne preto zamietnutie trápilo. Preflight, ktorý zablokuje platný súbor, je horší než pomalý, lebo volajúci dostane diagnostiku pôsobiacu autoritatívne a ukazujúcu na funkciu, ktorú dokument neobsahuje. Oprava mala dve časti: čítanie špecifikácie za jedným pravidlom a uvedomenie si, že pravidlo nedokázalo na úrovni, na akú sa pozeralo, rozoznať dva typy slovníkov
Ako funguje version preflight PDFlibPas pri ukladaní?
Ukladacia brána PrepareAndCheckSaveVersion porovnáva každý nepriamy objekt proti PDFFeatureRules a zlyhá na prvom pravidle, ktoré sedí a zároveň žiada viac, než cieľ dovoľuje. Cieľom je verzia dokumentu (alebo verzia pripnutá cez LockSaveVersion) plus Adobe extension level deklarovaný pod /Extensions /ADBE. Každý záznam TPDFFeatureRule nesie MinVersion, MinExtensionLevel, MatchKind typu fmkDictKey alebo fmkDictSubtype, reťazec Match, čitateľné meno Feature a voliteľný callback. AddRule registruje obyčajné verzné pravidlo; AddExtensionRule vždy pripne MinVersion na 17 a pridá extension level navrch, takže extenzijné pravidlo môže kedykoľvek uspokojiť len PDF 1.7 plus správna položka /Extensions. Keď brána zaklopie, požadovaná verzia a meno funkcie sa uschovajú pre volajúceho a kľúče 311, 312 a 313 GetInformation ich vystavia
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: požadovaná verzia, 312: funkcia, ktorá ju spustila,
// 313: verzia, na ktorú je ukladací cieľ zamknutý ('' keď odzamknutý)
Writeln('Needs ', Pdf.GetInformation(311),
' for ', Pdf.GetInformation(312),
', locked at [', Pdf.GetInformation(313), ']');
finally
Pdf.Free;
end;
end;
Prečo obyčajná CAD kresba zlyhala s chybou 602?
Tabuľka pravidiel obsahovala AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), ktorá sa spustila na akomkoľvek slovníku, ktorý mal len kľúč /Measure, a každý merací viewport ho má. Pole /VP strany drží slovníky viewportov, každý viewport ukazuje na svoj measure slovník cez /Measure a zhoda prítomnosti kľúča tam skončila bez pohľadu na to, čím ten measure slovník vlastne je. Feature sken pri načítaní potom mohol zvýšiť číslo verzie dokumentu na 1.7, ale nikdy nepíše deklaráciu /Extensions na účet vstupného súboru, takže ukladacia brána videla PDF 1.7 na extension level 0 a nahlásila 1.7 ExtensionLevel 3. To odmietnutie vymyslieť deklaráciu rozšírenia je zámerné: knižnica nepovyšuje potichu vstupný súbor, aby zakryla pravidlo, ktoré je zlé
Špecifikácia je v rectilinear prípade jednoznačná. Measure slovníky prišli v PDF 1.6 a ISO 32000-1 §12.9 dáva /Subtype predvolené RL, rectilineárny súradnicový systém opísaný vlastnou sadou položiek: mierka, formáty čísel X a Y, vzdialenosť a plocha. Geopriestorové meranie je neskorší prídavok z Adobe Extension Level 3 na vrchu PDF 1.7, identifikovaný cez /Subtype /GEO a nesúci geografické polia bodov, slovníky súradnicových systémov a zobrazovacie jednotky, štruktúry, ktorými prechádza článok o čítaní GeoPDF viewportov a polí GPTS a LPTS v Delphi. Oba slovníky visia na tom istom kľúči /Measure, takže pravidlo, ktoré zastane na kľúči, nemôže byť správne pre obe. Rozlišujúca informácia sedí úroveň nižšie, v samotnom measure slovníku
Čo opravená sada pravidiel stále vynucuje?
Oprava zmaže nepodmienené kľúčové pravidlo a nechá brány, ktoré opisujú skutočné verzné požiadavky. Strana nesúca /VP alebo /UserUnit stále potrebuje PDF 1.6 cez CB_PagePDF16Entries, kľúč /PtData stále potrebuje extension level 3 a CB_GeospatialDictionary rozhoduje, či je measure slovník geopriestorový, podľa jeho obsahu, nie podľa kľúča, ktorým prišiel
// Odstránené: každý slovník s kľúčom /Measure sa rátal ako geopriestorový
// 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;
Zdieľané Delphi a FPC regresie pripnú tú hranicu z oboch strán. Viewport, ktorého measure slovník vynechá /Subtype, aj taký, ktorý vypíše /RL, prejdú pri PDF 1.6, tá istá strana sa stále zamietne pri PDF 1.5 a feature detekcia už nehlási rozšírenie. Pridanie poľa /GPTS prevalí verdikt späť na 1.7 ExtensionLevel 3, čo prejde, akonáhle je extension level deklarovaný, a holý slovník /Subtype /GEO sa bez neho zamietne. Callback je konzervatívny zámerne: rectilineárny slovník, ktorý navyše nesie záujmiteľný kľúč /GCS alebo /PDU, sa berie ako geopriestorový, keďže tie kľúče nemajú v RL modeli žiadny význam
LockSaveVersion je miesto, kde sa tá zmena stane viditeľnou volajúcim. TPDFlib.LockSaveVersion prijíma '1.0' až '1.7', na čokoľvek iné vráti 0, pripne verziu dokumentu a zastaví volania na strane pisateľa pred jej potichu zvýšením, aj tak ale ukladacia brána beží proti zamknutej hodnote. S opravenými pravidlami sa CAD súbor zamknutý na 1.6 uloží čisto. Pravý GeoPDF zamknutý na 1.6 stále dostane 602, čo je správna odpoveď, a geopriestorové autorské volania, ako SetMeasureDictCoordinateSystem, si deklarujú extension level 3 samy, keď ten obsah staviate cez API
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
// Reálny obsah nad 1.6, napríklad GEO measure slovník
raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
[string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;
Prečo bol sken verzných pravidiel pomalší, než musel byť?
Sken kopíroval každý TPDFFeatureRule do lokálneho záznamu skôr, než ho otestoval, a keďže záznam drží dve polia AnsiString, každá kópia upravila dva referenčné počty a uvoľnila predchádzajúce hodnoty. Preflight navštívi každý uzol každého objektového stromu, vrátane skalárov, takže tá cena vynásobila počet objektov počtom pravidiel a pravidlá, ktoré sa na cieľovú verziu ani nevztahovali, sa najprv skopírovali a potom preskočili. Keďže PDFFeatureRules sa plní raz pri inicializácii jednotky a berie sa ako read-only, v3.539.17 podá položky tabuľky rovno do MatchSingleRule a RuleExceedsTarget, ktorých parametre const Rule prevezmú referenciu bez dotyku reťazcov
// Predtým: spravovaná kópia záznamu na pravidlo, na navštívený objekt
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
Continue;
// Potom: const parametre čítajú nemeniteľnú položku tabuľky na mieste
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;
Nameraný efekt je úzky a treba ho citovať takto. Benchmark kontroluje pole 20 000 číselných objektov proti cieľu PDF 1.4 desaťkrát za kolo; preložené FPC Win64 na -O2 klesol medián piatich kôl z 0,711 s na 0,203 s a beh oboch zostavení v opačnom poradí dal 0,459 s proti 0,150 s. To je zhruba 3-násobný zisk na samej ceste porovnávania pravidiel. Reálne ukladanie platí aj za odloženú feature detekciu, dekódovanie objektov a serializáciu, takže pomer sa neprenáša do celkového času ukladania. Poradie pravidiel, callbacky, verzné prahy a diagnostika prvého zlyhania ostali nezmenené a žiadne pravidlo sa nezakešovalo cez ukladania ani nepreskakovalo, aby sa tam dosiahlo
Čo skontrolovať, keď načítané PDF zlyhá vo version preflightu?
Prečítajte si kľúče 311 a 312, skôr než siahnete na verziu. Ak funkcia menuje geopriestorový slovník a súbor kreslí len rectilineárne merania, išlo o tento falošný poplach a aktuálne zostavenie uloží súbor nezmenený. Ak je funkcia skutočná, buď deklarujte rozšírenie, alebo sa zamknite na verziu, ktorá obsah poctivo pokrýva; zvýšenie verzie len na utíšenie brány schováva otázku, či downstream spotrebitelia prečítajú to, čo dodávate. Ten istý princíp ohraničených, dôkazmi podložených kontrol poháňa PDF/E-1 preflight režimu autora pre inžinierske dokumenty, kde CAD kresby stretajú normu súladu, nie verzné číslo
Kontroly verzného súladu, meracie aj geopriestorové slovníky a zamykanie verzie ukladania sú všetko súčasťou PDF Library for Delphi, toolkitu PDFlibPas pre vývojárov v Delphi, C++Builder a Lazarus