PDFlibPas (PDF Library for Delphi) sjekker hvert objekt mot en tabell av PDF-versjonsregler før den skriver en fil, og inntil nylig forvekslet den PDF-versjons-preflighten ordinære CAD-målingsordbøker med geospatiale. En CAD-tegning på én side lastet fint, så returnerte SaveToFile 0 med LastErrorCode 602 og krevde 1.7 ExtensionLevel 3. De korrigerte reglene behandler rectilinear /Measure-ordbøker (/Subtype /RL) som ren PDF 1.6 og reserverer utvidelsesporten for ekte geospatiale markører
Filen kom inn gjennom corpus-admisjon: én side, én optional content-gruppe, to rectilinear målingsviewports, den typen resultat en arkitektur-CAD-pakke skriver slik at et visningsprogram kan lese avstander rett av en plantegning. Ingenting ved den var eksotisk, noe som er nøyaktig grunnen til at nektelsen betydde noe. En preflight som blokkerer en gyldig fil, er verre enn en treg, fordi kalleren får en autoritativt utseende diagnose som peker på en funksjon dokumentet ikke inneholder. Fiksen tok to deler: spesifikasjonslesingen bak én regel, og erkjennelsen av at regelen ikke kunne skille to ordboktyper fra hverandre på nivået den så på
Hvordan virker PDFlibPas versjons-preflight ved lagring?
Lagringsporten, PrepareAndCheckSaveVersion, sammenligner hvert indirekte objekt mot PDFFeatureRules og feiler på den første regelen som både matcher og trenger mer enn målet tillater. Målet er dokumentversjonen (eller versjonen festet av LockSaveVersion), pluss Adobe-utvidelsesnivået deklarert under /Extensions /ADBE. Hver TPDFFeatureRule-post bærer en MinVersion, et MinExtensionLevel, en MatchKind som fmkDictKey eller fmkDictSubtype, en Match-streng, et menneskelesbart Feature-navn og en valgfri callback. AddRule registrerer en ren versjonsregel; AddExtensionRule fester alltid MinVersion på 17 og legger et utvidelsesnivå oppå, så en utvidelsesregel bare kan tilfredsstilles av PDF 1.7 pluss riktig /Extensions-oppføring. Når porten utløser, beholdes den krevde versjonen og funksjonsnavnet for kalleren, og GetInformation-nøklene 311, 312 og 313 eksponerer dem
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: krevd versjon, 312: funksjonen som utløste den,
// 313: versjonen lagringsmålet er låst på ('' når ulåst)
Writeln('Needs ', Pdf.GetInformation(311),
' for ', Pdf.GetInformation(312),
', locked at [', Pdf.GetInformation(313), ']');
finally
Pdf.Free;
end;
end;
Hvorfor feilet en vanlig CAD-tegning med feil 602?
Regeltabellen inneholdt AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), som utløste på enhver ordbok som bare hadde en /Measure-nøkkel, og hver målingsviewport har en. Sidens /VP-array holder viewport-ordbøker, hver viewport peker på målingsordboken sin gjennom /Measure, og nøkkel-tilstedeværelse-matchet stoppet der uten å se på hva målingsordboken faktisk var. Lastetidens funksjonsskann kunne da heve dokumentversjonsnummeret til 1.7, men den skriver aldri en /Extensions-deklarasjon på vegne av en inputfil, så lagringsporten så PDF 1.7 på utvidelsesnivå 0 og rapporterte 1.7 ExtensionLevel 3. Den nektelsen til å finne opp en utvidelsesdeklarasjon er bevisst: biblioteket forfremmer ikke stille en inputfil for å plaster over en regel som tar feil
Spesifikasjonen er entydig om rectilinear-tilfellet. Measure-ordbøker kom i PDF 1.6, og ISO 32000-1 §12.9 gir /Subtype standardverdien RL, et rectilinear koordinatsystem beskrevet av sitt eget sett med oppføringer: skaleringsforhold, tallformat for X og Y, avstand og areal. Geospatial måling er det senere tillegget fra Adobe Extension Level 3 oppå PDF 1.7, identifisert ved /Subtype /GEO og bærende på geografiske punkt-arrayer, koordinatsystemordbøker og visningsenheter, strukturene som går gjennom i lesing av GeoPDF-viewports, GPTS- og LPTS-arrayer i Delphi. Begge ordbøkene henger på samme /Measure-nøkkel, så enhver regel som stopper ved nøkkelen, kan ikke være riktig for begge. Den skillende informasjonen ligger ett nivå lenger ned, i målingsordboken selv
Hva håndhever det korrigerte regelsettet fortsatt?
Fiksen sletter den ubetingede nøkkelregelen og lar portene som beskriver ekte versjonskrav, stå. En side som bærer /VP eller /UserUnit, trenger fortsatt PDF 1.6 gjennom CB_PagePDF16Entries, en /PtData-nøkkel trenger fortsatt utvidelsesnivå 3, og CB_GeospatialDictionary avgjør om en målingsordbok er geospatial etter innholdet sitt snarere enn etter nøkkelen som nådde den
// Fjernet: enhver ordbok med en /Measure-nøkkel telte som 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;
De delte Delphi- og FPC-regresjonene fester den grensen fra begge sider. En viewport hvis målingsordbok utelater /Subtype, og én som staver ut /RL, går begge gjennom på PDF 1.6, samme side avvises fortsatt på PDF 1.5, og funksjonsdeteksjonen rapporterer ikke lenger en utvidelse for den. Å legge til en /GPTS-array flipper dommen tilbake til 1.7 ExtensionLevel 3, som går gjennom når utvidelsesnivået er deklarert, og en naken /Subtype /GEO-ordbok nektes uten den. Callbacken er konservativ av design: en rectilinear ordbok som også bærer en løs /GCS- eller /PDU-nøkkel, behandles som geospatial, siden de nøklene ikke har noen betydning i RL-modellen
LockSaveVersion er der denne endringen blir synlig for kallerne. TPDFlib.LockSaveVersion godtar '1.0' gjennom '1.7', returnerer 0 for alt annet, fester dokumentversjonen og stopper forfattersidens kall fra å heve den i stillhet, men lagringsporten kjører fortsatt mot den låste verdien. Med de korrigerte reglene lagrer en CAD-fil låst på 1.6 rent. En ekte GeoPDF låst på 1.6 får fortsatt 602, som er det riktige svaret, og geospatiale forfatterkall som SetMeasureDictCoordinateSystem deklarerer utvidelsesnivå 3 selv når du bygger det innholdet gjennom API-et
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
// Ekte innhold over 1.6, for eksempel en GEO measure-ordbok
raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
[string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;
Hvorfor var versjonsregel-skannen tregere enn den trengte å være?
Skannen kopierte hver TPDFFeatureRule inn i en lokal post før den testet den, og fordi posten holder to AnsiString-felt, justerte hver kopi to referansetellinger og slapp de forrige verdiene. Preflighten besøker hver node i hvert objekt-tre, skalarer inkludert, så den kostningen multipliserte objektantall med regelantall, og regler som ikke engang gjaldt målversjonen, ble kopiert først og hoppet over etterpå. Siden PDFFeatureRules fylles én gang ved unit-initialisering og behandles som skrivebeskyttet, sender v3.539.17 tabelloppføringer rett til MatchSingleRule og RuleExceedsTarget, hvis const Rule-parametre tar en referanse uten å røre strengene
// Før: en administrert postkopi per regel, per besøkt objekt
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
Continue;
// Etter: const-parametre leser den uforanderlige tabelloppføringen på stedet
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;
Den målte effekten er smal og bør siteres slik. Benchmarken sjekker en array på 20 000 numeriske objekter mot et PDF 1.4-mål ti ganger per runde; bygget med FPC Win64 ved -O2, falt medianen av fem runder fra 0,711 s til 0,203 s, og å kjøre de to byggene i omvendt rekkefølge ga 0,459 s mot 0,150 s. Det er omtrent en 3x gevinst på regelmatchingsstien alene. En ekte lagring betaler også for utsatt funksjonsdeteksjon, objektdekoding og serialisering, så ratioen bæres ikke over på total lagringstid. Regelrekkefølge, callbacks, versjonsterskler og den første-feil-diagnostikken er uendret, og ingen regel ble cachet på tvers av lagringer eller hoppet over for å oppnå det
Hva bør du sjekke når en lastet PDF feiler versjons-preflighten?
Les nøklene 311 og 312 før du rører versjonen. Hvis funksjonen navngir en geospatial ordbok og filen bare tegner rectilinear målinger, var det denne falske positiven, og et nåværende bygg lagrer filen uendret. Hvis funksjonen er ekte, deklarer enten utvidelsen eller lås til en versjon som ærlig inneholder innholdet; å heve versjonen bare for å få porten til å tie, skjuler spørsmålet om hvorvidt nedstrømsforbrukere kan lese det du leverer. Samme prinsipp om avgrensede, evidensbaserte sjekker driver PDF/E-1 forfattermodus-preflighten for ingeniørdokumenter, der CAD-tegninger møter en konformitetsstandard snarere enn et versjonsnummer
Versjonskonformitetssjekker, målings- og geospatiale ordbøker og lagringsversjonslåsing er alle del av PDF Library for Delphi, PDFlibPas-verktøykassen for Delphi-, C++Builder- og Lazarus-utviklere