PDFlibPas (PDF Library for Delphi) verifică fiecare obiect împotriva unui tabel de reguli de versiune PDF înainte să scrie un fișier, și până de recent acel preflight de versiune confunda dicționarele obișnuite de măsurare CAD cu cele geospatiale. Un desen CAD cu o singură pagină se încărca bine, apoi SaveToFile întorcea 0 cu LastErrorCode 602 și cerea 1.7 ExtensionLevel 3. Regulile corectate tratează dicționarele /Measure rectilinii (/Subtype /RL) ca PDF 1.6 simplu și rezervă poarta de extensie pentru markerii geospatiali adevărați
Fișierul a venit prin admiterea de corpus: o pagină, un grup optional-content, două viewports de măsurare rectilinii, genul de output pe care îl scrie un pachet CAD de arhitectură pentru ca un vizualizator să poată citi distanțe de pe planul etajului. Nimic exotic, ceea ce e exact motivul pentru care refuzul a contat. Un preflight care blochează un fișier valid e mai rău decât unul lent, pentru că apelantul primește un diagnostic cu aspect autoritar care arată spre o funcționalitate pe care documentul nu o conține. Reparația a avut două părți: lectura specificației din spatele unei reguli și realizarea că regula nu putea deosebi două tipuri de dicționar la nivelul la care se uita
Cum funcționează preflight-ul de versiune din PDFlibPas la salvare?
Poarta de salvare, PrepareAndCheckSaveVersion, compară fiecare obiect indirect cu PDFFeatureRules și eșuează la prima regulă care se potrivește și care cere mai mult decât ținta permite. Ținta este versiunea documentului (sau versiunea blocată prin LockSaveVersion), plus nivelul de extensie Adobe declarat sub /Extensions /ADBE. Fiecare înregistrare TPDFFeatureRule cară un MinVersion, un MinExtensionLevel, un MatchKind precum fmkDictKey sau fmkDictSubtype, un șir Match, un nume de Feature lizibil pentru om și un callback opțional. AddRule înregistrează o regulă de versiune simplă; AddExtensionRule fixează mereu MinVersion la 17 și adaugă un nivel de extensie peste, deci o regulă de extensie poate fi satisfăcută doar de PDF 1.7 plus intrarea /Extensions corectă. Când poarta se declanșează, versiunea cerută și numele funcționalității sunt păstrate pentru apelant, iar cheile 311, 312 și 313 din GetInformation le expun
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: versiunea cerută, 312: funcționalitatea care a declanșat-o,
// 313: versiunea la care e blocată ținta de salvare ('' când nu e blocată)
Writeln('Needs ', Pdf.GetInformation(311),
' for ', Pdf.GetInformation(312),
', locked at [', Pdf.GetInformation(313), ']');
finally
Pdf.Free;
end;
end;
De ce un desen CAD obișnuit eșua cu eroarea 602?
Tabelul de reguli conținea AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), care se declanșa pe orice dicționar care avea pur și simplu o cheie /Measure, iar fiecare viewport de măsurare are una. Tabloul /VP al paginii ține dicționare de viewport, fiecare viewport arată spre dicționarul său de măsurare prin /Measure, iar potrivirea pe prezența cheii se oprea acolo, fără să se uite ce era de fapt dicționarul de măsurare. Scanul de funcționalități de la încărcare putea apoi ridica numărul versiunii documentului la 1.7, dar nu scrie niciodată o declarație /Extensions în numele unui fișier de intrare, deci poarta de salvare vedea PDF 1.7 la nivel de extensie 0 și raporta 1.7 ExtensionLevel 3. Refuzul acela de a inventa o declarație de extensie e deliberat: biblioteca nu promovează în tăcere un fișier de intrare ca să acopere o regulă greșită
Specificația e neechivocă în cazul rectiliniu. Dicționarele Measure au sosit în PDF 1.6, iar ISO 32000-1 §12.9 dă lui /Subtype un implicit RL, un sistem de coordonate rectiliniu descris de propriul său set de intrări: raport de scară, formate de numere X și Y, distanță și arie. Măsurarea geospatială e adaosul ulterior din Adobe Extension Level 3 peste PDF 1.7, identificat prin /Subtype /GEO și carând tablouri de puncte geografice, dicționare de sisteme de coordonate și unități de afișare, structurile parcurse în citirea viewports GeoPDF și a tablourilor GPTS și LPTS în Delphi. Ambele dicționare atârnă de aceeași cheie /Measure, deci orice regulă care se oprește la cheie nu poate fi corectă pentru ambele. Informația distinctivă stă un nivel mai jos, în dicționarul de măsurare însuși
Ce impune în continuare setul de reguli corectat?
Reparația șterge regula necondiționată de cheie și lasă porțile care descriu cerințe reale de versiune. O pagină care cară /VP sau /UserUnit mai are nevoie de PDF 1.6 prin CB_PagePDF16Entries, o cheie /PtData mai are nevoie de nivelul de extensie 3, iar CB_GeospatialDictionary decide dacă un dicționar de măsurare e geospatial după conținutul lui, nu după cheia prin care a ajuns la el
// Eliminat: orice dicționar cu o cheie /Measure era numărat ca 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;
Regresiile partajate Delphi și FPC fixează granița aceea din ambele părți. Un viewport al cărui dicționar de măsurare omite /Subtype și unul care scrie explicit /RL trec amândouă la PDF 1.6, aceeași pagină e respinsă în continuare la PDF 1.5, iar detecția de funcționalități nu mai raportează o extensie pentru ea. Adăugarea unui tablou /GPTS întoarce verdictul înapoi la 1.7 ExtensionLevel 3, care trece odată ce nivelul de extensie e declarat, iar un dicționar gol cu /Subtype /GEO e refuzat fără el. Callback-ul e conservator din principiu: un dicționar rectiliniu care cară și o cheie rătăcită /GCS sau /PDU e tratat ca geospatial, pentru că acele chei nu au sens în modelul RL
LockSaveVersion e locul în care schimbarea devine vizibilă pentru apelanți. TPDFlib.LockSaveVersion acceptă '1.0' până la '1.7', întoarce 0 pentru orice altceva, blochează versiunea documentului și împiedică apelurile din partea scriitorului să o ridice în tăcere, dar poarta de salvare rulează în continuare împotriva valorii blocate. Cu regulile corectate, un fișier CAD blocat la 1.6 se salvează curat. Un GeoPDF autentic blocat la 1.6 primește în continuare 602, care e răspunsul corect, iar apelurile de autorat geospatial precum SetMeasureDictCoordinateSystem declară ele însele nivelul de extensie 3 când construiți acel conținut prin 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
// Conținut real peste 1.6, de exemplu un dicționar measure GEO
raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
[string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;
De ce scanul regulilor de versiune era mai lent decât trebuia?
Scanul copia fiecare TPDFFeatureRule într-un record local înainte să o testeze, și pentru că recordul ține două câmpuri AnsiString, fiecare copie ajusta două contoare de referințe și elibera valorile anterioare. Preflight-ul vizitează fiecare nod al fiecărui arbore de obiecte, scalarii incluși, deci acel cost multiplica numărul de obiecte cu numărul de reguli, iar regulile care nici măcar nu se aplicau versiunii țintă erau copiate mai întâi și sărite apoi. Întrucât PDFFeatureRules e umplut o dată la inițializarea unit-ului și tratat ca read-only, v3.539.17 trece intrările tabelei direct spre MatchSingleRule și RuleExceedsTarget, ale căror parametri const Rule iau o referință fără să atingă șirurile
// Înainte: o copie de record gestionat per regulă, per obiect vizitat
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
Continue;
// După: parametrii const citesc intrarea imuabilă a tabelei în loc
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;
Efectul măsurat e îngust și ar trebui citat ca atare. Benchmark-ul verifică un tablou de 20.000 de obiecte numerice împotriva unei ținte PDF 1.4 de zece ori per rundă; compilat cu FPC Win64 la -O2, medianul a cinci runde a coborât de la 0,711 s la 0,203 s, iar rularea celor două build-uri în ordine inversă a dat 0,459 s față de 0,150 s. E aproximativ un câștig de 3x doar pe calea de potrivire a regulilor. O salvare reală plătește și detecția întârziată de funcționalități, decodarea obiectelor și serializarea, deci raportul nu se transpune pe timpul total de salvare. Ordinea regulilor, callback-urile, pragurile de versiune și diagnosticul primei eșecări sunt neschimbate, iar nicio regulă n-a fost ținută în cache peste salvări sau sărită ca să se ajungă aici
Ce verificați când un PDF încărcat pică la preflight-ul de versiune?
Citiți cheile 311 și 312 înainte să atingeți versiunea. Dacă funcționalitatea numește un dicționar geospatial și fișierul doar desenează măsurători rectilinii, acesta era falsul pozitiv de față, iar un build curent salvează fișierul neschimbat. Dacă funcționalitatea e autentică, ori declarați extensia, ori blocați o versiune care conține onest conținutul; ridicarea versiunii doar ca să amuțească poarta ascunde întrebarea dacă consumatorii din aval pot citi ce livrați. Același principiu al verificărilor mărginite și susținute de dovezi guvernează preflight-ul PDF/E-1 în mod autor pentru documente de inginerie, unde desenele CAD întâlnesc un standard de conformitate, nu un număr de versiune
Verificările de conformitate de versiune, dicționarele de măsurare și geospatiale și blocarea versiunii de salvare fac toate parte din PDF Library for Delphi, kit-ul PDFlibPas pentru dezvoltatorii Delphi, C++Builder și Lazarus