PDFlibPas (PDF Library for Delphi) proverava svaki objekt naspram tabele pravila PDF verzije pre nego što upiše fajl, i do skoro je taj PDF version preflight uzimao obične CAD mjerne rečnike za geospatijalne. CAD crtež od jedne stranice uredno se učitao, a onda je SaveToFile vratio 0 sa LastErrorCode 602 i zahtevao 1.7 ExtensionLevel 3. Ispravljena pravila tretiraju rectilinear /Measure rečnike (/Subtype /RL) kao običan PDF 1.6 i ostavljaju extension kapiju za prave geospatijalne markere
Fajl je ušao kroz corpus prijem: jedna stranica, jedna optional-content grupa, dva rectilinear mjerna viewport-a, vrsta izlaza koji arhitektonski CAD paket piše da bi pregledač mogao da očita rastojanja sa osnova plana. Ništa kod njega nije bilo egzotično, i baš zato je odbijanje i bilo važno. Preflight koji blokira validan fajl je gori od sporog, jer pozivalac dobija dijagnostiku koja deluje autoritetno i pokazuje na mogućnost koju dokument uopšte ne sadrži. Popravka je imala dva dela: čitanje specifikacije iza jednog pravila, i spoznaju da pravilo na nivou na kom je gledalo ne može da razlikuje dva tipa rečnika
Kako radi save-time version preflight u PDFlibPas-u?
Save kapija, PrepareAndCheckSaveVersion, poredi svaki indirektan objekt naspram PDFFeatureRules i pada na prvom pravilu koje se i poklopi i traži više nego što target dozvoljava. Target je verzija dokumenta (ili verzija prikovana kroz LockSaveVersion), plus Adobe extension level deklarisan pod /Extensions /ADBE. Svaki TPDFFeatureRule zapis nosi MinVersion, MinExtensionLevel, MatchKind poput fmkDictKey ili fmkDictSubtype, Match string, čoveku čitljivo Feature ime i opcionu callback funkciju. AddRule registruje obično version pravilo; AddExtensionRule uvek pribija MinVersion na 17 i dodaje extension level preko toga, pa extension pravilo može zadovoljiti samo PDF 1.7 plus pravi /Extensions unos. Kada kapija zapne, potrebna verzija i ime mogućnosti čuvaju se za pozivaoca, i GetInformation ključevi 311, 312 i 313 ih izlažu
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: potrebna verzija, 312: mogućnost koja ju je okidala,
// 313: verzija na koju je save target zaključan ('' kad nije zaključan)
Writeln('Needs ', Pdf.GetInformation(311),
' for ', Pdf.GetInformation(312),
', locked at [', Pdf.GetInformation(313), ']');
finally
Pdf.Free;
end;
end;
Zašto je običan CAD crtež pao sa greškom 602?
Tabela pravila je sadržala AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), koja je pucala na svaki rečnik koji samo ima /Measure ključ, a svaki mjerni viewport ga ima. /VP niz stranice drži viewport rečnike, svaki viewport pokazuje na svoj measure rečnik kroz /Measure, i poklapanje po prisustvu ključa tu je stalo, bez gledanja šta je measure rečnik zapravo bio. Load-time feature sken mogao je tada da podigne broj verzije dokumenta na 1.7, ali nikada ne upisuje /Extensions deklaraciju u ime ulaznog fajla, pa je save kapija videla PDF 1.7 na extension level 0 i prijavila 1.7 ExtensionLevel 3. To odbijanje da izmišlja extension deklaraciju je namerno: biblioteka ne unapređuje tiho ulazni fajl da bi prekriala pogrešno pravilo
Specifikacija je za rectilinear slučaj nedvosmislena. Measure rečnici su stigli sa PDF 1.6, i ISO 32000-1 §12.9 daje /Subtype-u podrazumevanu vrednost RL, rectilinear koordinatni sistem opisan sopstvenim setom unosa: odnos razmere, X i Y formati brojeva, rastojanje i površina. Geospatijalno merenje je kasniji dodatak iz Adobe Extension Level 3 preko PDF 1.7, prepoznatljiv po /Subtype /GEO, sa geografskim nizovima tačaka, rečnicima koordinatnih sistema i jedinicama prikaza, strukturama kroz koje prolazi čitanje GeoPDF viewport-a, GPTS i LPTS nizova u Delphi-ju. Oba rečnika vise sa istog /Measure ključa, pa nijedno pravilo koje stane na ključu ne može biti ispravno za oba. Razlikujući podatak sedi jedan nivo niže, u samom measure rečniku
Šta ispravljeni set pravila i dalje nameće?
Popravka briše bezuslovno ključ pravilo i ostavlja kapije koje opisuju stvarne zahteve verzije. Stranica koja nosi /VP ili /UserUnit i dalje traži PDF 1.6 kroz CB_PagePDF16Entries, /PtData ključ i dalje traži extension level 3, a CB_GeospatialDictionary odlučuje da li je measure rečnik geospatijalan po svom sadržaju, a ne po ključu kojim je došao
// Uklonjeno: svaki rečnik sa /Measure ključem računao se kao geospatijalan
// 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;
Zajedničke Delphi i FPC regresije pribijaju tu granicu sa obe strane. Viewport čiji measure rečnik izostavlja /Subtype i onaj koji izričito napiše /RL oba prolaze na PDF 1.6, ista stranica se i dalje odbija na PDF 1.5, a feature detekcija više ne prijavljuje extension za nju. Dodavanje /GPTS niza vraća presudu na 1.7 ExtensionLevel 3, što prolazi čim se extension level deklariše, a gol /Subtype /GEO rečnik se odbija bez toga. Callback je po dizajnu konzervativan: rectilinear rečnik koji nosi i odmetnuti /GCS ili /PDU ključ tretira se kao geospatijalan, jer ti ključevi nemaju značenje u RL modelu
LockSaveVersion je mesto na kom ova promena postaje vidljiva pozivaocima. TPDFlib.LockSaveVersion prihvata '1.0' do '1.7', za sve ostalo vraća 0, pribija verziju dokumenta i sprečava pisane pozive da je tiho podignu, a save kapija i dalje radi protiv zaključane vrednosti. Sa ispravljenim pravilima, CAD fajl zaključan na 1.6 čuva se čisto. Pravi GeoPDF zaključan na 1.6 i dalje dobija 602, što je ispravan odgovor, a geospatijalni autorski pozivi poput SetMeasureDictCoordinateSystem sami deklarišu extension level 3 kad taj sadržaj gradite kroz 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
// Pravi sadržaj iznad 1.6, na primer GEO measure rečnik
raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
[string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;
Zašto je sken version pravila bio sporiji nego što je trebalo?
Sken je kopirao svaki TPDFFeatureRule u lokalni zapis pre testiranja, i pošto zapis nosi dva AnsiString polja, svaka kopija je korigovala dva brojača referenci i oslobađala prethodne vrednosti. Preflight posećuje svaki čvor svakog objektnog drveta, skalare uključivo, pa se taj trošak množio broj objekata sa brojem pravila, a pravila koja se na ciljnu verziju uopšte ne odnose kopirala su se prvo pa preskakala posle. Pošto je PDFFeatureRules popunjen jednom pri inicijalizaciji jedinice i tretiran kao read-only, v3.539.17 prosleđuje unose tabele pravo u MatchSingleRule i RuleExceedsTarget, čiji const Rule parametri uzimaju referencu bez diranja stringova
// Pre: upravljana kopija zapisa po pravilu, po posećenom objektu
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
Continue;
// Posle: const parametri čitaju nepromenljivi unos 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;
Izmereni efekat je uzak i tako treba i citirati ga. Benchmark proverava niz od 20.000 numeričkih objekata naspram PDF 1.4 targeta deset puta po krugu; kompajliran sa FPC Win64 na -O2, median od pet krugova pao je sa 0,711 s na 0,203 s, a pokretanje dva builda obrnutim redosledom dalo je 0,459 s naspram 0,150 s. To je otprilike 3x dobitak samo na putanji poklapanja pravila. Pravo čuvanje plaća i odloženu feature detekciju, dekodovanje objekata i serijalizaciju, pa se proporcija ne prenosi na ukupno vreme čuvanja. Redosled pravila, callback-ovi, pragovi verzija i dijagnostika prvog pada su nepromenjeni, i nijedno pravilo nije keširano između čuvanja niti preskočeno da bi se došlo dovde
Šta proveriti kad učitani PDF padne na version preflight-u?
Pročitajte ključeve 311 i 312 pre nego što dirate verziju. Ako mogućnost imenuje geospatijalni rečnik a fajl samo crta rectilinear merenja, to je bio ovaj lažni pozitiv, i aktuelni build čuva fajl nepromenjen. Ako je mogućnost prava, ili deklarišite extension ili se zaključajte na verziju koja sadržaj iskreno sadrži; podizanje verzije samo da se kapija utiša krije pitanje mogu li nizvodni potrošači da pročitaju ono što isporučujete. Isti princip ograničenih, dokazima potkrepljenih provera pokreće PDF/E-1 author-mode preflight za inženjerske dokumente, gde CAD crteži meru sa standardom usaglašenosti, a ne sa brojem verzije
Provere usaglašenosti verzije, mjerni i geospatijalni rečnici i zaključavanje verzije čuvanja deo su PDF Library for Delphi, PDFlibPas alata za Delphi, C++Builder i Lazarus developere