PDFlibPas (PDF Library for Delphi) prieš rašydamas failą kiekvieną objektą patikrina pagal PDF versijos taisyklių lentelę, ir iki neseniai tas PDF versijos preflight paprastus CAD matavimo žodynus supainiodavo su geospatial. Vienpuslapio CAD brėžinys įsikeldavo puikiai, o tada SaveToFile grąžindavo 0 su LastErrorCode 602 ir reikalaudavo 1.7 ExtensionLevel 3. Pataisytos taisyklės rectilinear /Measure žodynus (/Subtype /RL) traktuoja kaip paprastą PDF 1.6, o extension vartus palieka tikriems geospatial ženklams
Failas atėjo per korpuso priėmimą: vienas puslapis, viena optional-content grupė, du rectilinear matavimo viewportai – tokia išvestis, kokią rašo architektūrinis CAD paketas, kad peržiūros programa galėtų perskaityti atstumus nuo bendrojo plano. Jame nebuvo nieko egzotiško, ir būtent todėl tas atmetimas ir svarbus. Preflight, kuris blokuoja teisingą failą, yra blogesnis už lėtą, nes kviečiantysis gauna autoritetingai atrodančią diagnostiką, rodančią į funkciją, kurios dokumente nėra. Pataisa turėjo dvi dalis: specifikacijos skaitymą už vienos taisyklės ir supratimą, kad taisyklė negalėjo atskirti dviejų žodynų tipų tame lygmenyje, kuriame žiūrėjo
Kaip veikia PDFlibPas įrašymo metu vykstantis versijos preflight?
Įrašymo vartai PrepareAndCheckSaveVersion kiekvieną netiesioginį objektą lygina su PDFFeatureRules ir žlunga ties pirmąja taisykle, kuri ir sutampa, ir reikalauja daugiau, nei leidžia taikinys. Taikinys yra dokumento versija (arba versija, užfiksuota LockSaveVersion), plius Adobe extension lygis, deklaruotas po /Extensions /ADBE. Kiekvienas TPDFFeatureRule įrašas neša MinVersion, MinExtensionLevel, MatchKind, tokį kaip fmkDictKey ar fmkDictSubtype, Match eilutę, žmogui skaitomą Feature pavadinimą ir pasirinktinį callback. AddRule registruoja paprastą versijos taisyklę; AddExtensionRule visuomet užkalė MinVersion ties 17 ir ant viršaus prideda extension lygį, tad extension taisyklę gali patenkinti tik PDF 1.7 su tinkamu /Extensions įrašu. Kai vartai suveikia, reikalaujama versija ir funkcijos pavadinimas išsaugomi kviečiančiajam, o GetInformation raktai 311, 312 ir 313 juos atidengia
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: reikalaujama versija, 312: funkcija, kuri ją sužadino,
// 313: versija, kuria užfiksuotas įrašymo taikinys ('' kai neužfiksuotas)
Writeln('Needs ', Pdf.GetInformation(311),
' for ', Pdf.GetInformation(312),
', locked at [', Pdf.GetInformation(313), ']');
finally
Pdf.Free;
end;
end;
Kodėl paprastas CAD brėžinys žlugo su klaida 602?
Taisyklių lentelėje buvo AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), kuri suveikdavo bet kuriam žodynui, kuris tiesiog turėjo /Measure raktą, o kiekvienas matavimo viewportas jį turi. Puslapio /VP masyvas laiko viewport žodynus, kiekvienas viewportas į savo measure žodyną rodo per /Measure, o rakto buvimo atitikimas ten ir sustodavo, nežiūrėdamas, kuo tas measure žodynas iš tikrųjų buvo. Įkėlimo metu vykstanti funkcijų patikra tada galėjo pakelti dokumento versijos numerį iki 1.7, bet ji niekada neparašo /Extensions deklaracijos įvesties failo vardu, tad įrašymo vartai matė PDF 1.7 su extension lygiu 0 ir pranešė 1.7 ExtensionLevel 3. Tas atsisakymas išsigalvoti extension deklaraciją sąmoningas: biblioteka tyliai nepakelia įvesties failo, kad uždengtų neteisingą taisyklę
Specifikacija dėl rectilinear atvejo kalba nedviprasmiškai. Measure žodynai atsirado PDF 1.6, ir ISO 32000-1 §12.9 /Subtype duoda numatytąją RL – rectilinear koordinačių sistemą, aprašytą savu įrašų rinkiniu: mastelio santykiu, X ir Y skaičių formatais, atstumu ir plotu. Geospatial matavimas yra vėlesnis Adobe Extension Level 3 priedas ant PDF 1.7, atpažįstamas per /Subtype /GEO ir nešantis geografinių taškų masyvus, koordinačių sistemų žodynus ir rodymo vienetus – struktūras, kurias apeina GeoPDF viewport, GPTS ir LPTS masyvų skaitymas Delphi. Abu žodynai kabo ant to paties /Measure rakto, tad bet kuri taisyklė, sustojanti ties raktu, negali būti teisinga abiem. Skiriamoji informacija sėdi lygmeniu žemiau – pačiame measure žodyne
Ką pataisyta taisyklių aibė vis tiek reikalauja?
Pataisa ištrina besąlyginę rakto taisyklę ir palieka vartus, aprašančius tikras versijos sąlygas. Puslapis, nešantis /VP arba /UserUnit, vis tiek reikalauja PDF 1.6 pro CB_PagePDF16Entries, /PtData raktas vis tiek reikalauja extension lygio 3, o CB_GeospatialDictionary sprendžia, ar measure žodynas geospatial, pagal jo turinį, o ne pagal raktą, kuriuo jis atkeliavo
// Pašalinta: kiekvienas žodynas su /Measure raktu skaičiuotas kaip geospatial
// 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;
Bendra Delphi ir FPC regresija tą ribą užkalė iš abiejų pusių. Viewportas, kurio measure žodynas praleidžia /Subtype, ir viewportas, kuris jį išrašo kaip /RL, abu praeina ties PDF 1.6, tas pats puslapis ties PDF 1.5 vis tiek atmetamas, o funkcijų aptikimas daugiau nebepraneša extension jo atžvilgiu. Pridėjus /GPTS masyvą, verdiktas sugrįžta į 1.7 ExtensionLevel 3, kuris praeina, tik paskelbus extension lygį, o plikas /Subtype /GEO žodynas be jo atmetamas. Callback sąmoningai konservatyvus: rectilinear žodynas, kuris dar neša atsitiktinai užklydusį /GCS arba /PDU raktą, laikomas geospatial, nes tie raktai RL modelyje neturi prasmės
LockSaveVersion – vieta, kur šis pakeitimas tampa matomas kviečiantiesiems. TPDFlib.LockSaveVersion priima '1.0' iki '1.7', bet ką kito grąžina 0, užkalė dokumento versiją ir sustabdo rašytojo pusės iškvietimus nuo tylios jos pakėlimo, tačiau įrašymo vartai vis tiek paleidžiami prieš užkalėtą reikšmę. Su pataisytomis taisyklėmis CAD failas, užkalėtas ties 1.6, įrašosi švariai. Tikras GeoPDF, užkalėtas ties 1.6, vis tiek gauna 602 – tai teisingas atsakymas, o geospatial autorinimo iškvietimai, tokie kaip SetMeasureDictCoordinateSystem, patys deklaruoja extension lygį 3, kai tą turinį statote pro 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
// Tikras turinys aukščiau 1.6, pavyzdžiui GEO measure žodynas
raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
[string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;
Kodėl versijos taisyklių skenavimas buvo lėtesnis, nei galėjo būti?
Skenavimas kiekvieną TPDFFeatureRule nukopijuodavo į vietinį įrašą, dar prieš jį tikrindamas, ir kadangi įrašas laiko du AnsiString laukus, kiekviena kopija pakeldavo du nuorodų skaitiklius ir atlaisvindavo ankstesnes reikšmes. Preflight aplanko kiekvieną kiekvieno objektų medžio mazgą, įskaitant skaliarus, tad ta kaina sudaugino objektų skaičių iš taisyklių skaičiaus, o taisyklės, kurios net netaikėsi taikinio versijai, pirmiausia buvo kopijuojamos ir tik paskui praleidžiamos. Kadangi PDFFeatureRules pildomas vienąkart inicializuojant unitą ir traktuojamas kaip tik skaitomas, v3.539.17 lentelės įrašus perduoda tiesiai į MatchSingleRule ir RuleExceedsTarget, kurių const Rule parametrai paima nuorodą, neliesdami eilučių
// Anksčiau: tvarkomas įrašo kopijavimas kiekvienai taisyklei, kiekvienam aplankytam objektui
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
Continue;
// Dabar: const parametrai skaito nekintamą lentelės įrašą vietoje
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;
Išmatuotas efektas siauras ir jį verta cituoti taip pat. Benchmarkas dešimt kartų per apyrankę tikrina 20 000 skaitinių objektų masyvą prieš PDF 1.4 taikinį; sukompiliuotas FPC Win64 su -O2, penkių apyrankių median nukrito nuo 0,711 s iki 0,203 s, o abu variantus paleidus atvirkštine tvarka gauta 0,459 s prieš 0,150 s. Tai maždaug 3x laimėjimas vien taisyklių atitikimo kelyje. Tikras įrašymas dar sumoka už atidėtą funkcijų aptikimą, objektų dekodavimą ir serializaciją, tad santykis nesitęsia iki viso įrašymo laiko. Taisyklių tvarka, callback, versijų slenksčiai ir pirmojo žlugimo diagnostika nesikeitė, ir nė viena taisyklė nebuvo talpinama į podėlį tarp įrašymų ar praleidama, kad to pasiektume
Ką tikrinti, kai įkeltas PDF nepraeina versijos preflight?
Prieš liečdami versiją, perskaitykite raktus 311 ir 312. Jei funkcija vadina geospatial žodyną, o failas tik piešia rectilinear matavimus, tai buvo šis klaidingas teigiamas, ir dabartinė versija failą įrašo nepakeistą. Jei funkcija tikra, arba deklaruokite extension, arba užrakinkite versiją, kuri sąžiningai turi tą turinį; versijos pakėlimas vien tam, kad nutylėtų vartai, nuslepia klausimą, ar vėlesni vartotojai sugebės perskaityti tai, ką pristatote. Tą patį apibrėžtų, įrodymais paremtų patikrų principą vysto PDF/E-1 author-mode preflight inžineriniams dokumentams, kur CAD brėžiniai susitinka su atitikimo standartu, o ne versijos numeriu
Versijos atitikties patikros, matavimo ir geospatial žodynai bei įrašymo versijos fiksavimas – visa tai yra PDF Library for Delphi, PDFlibPas įrankių rinkinys Delphi, C++Builder ir Lazarus kūrėjams dalis