Tehnički članak

PDF version preflight u Delphiju: pravila Measure RL i GEO

PDFlibPas (PDF Library for Delphi) provjerava svaki objekt protiv tablice PDF version pravila prije nego zapiše datoteku, i do nedavno je taj PDF version preflight obične CAD rječnike za mjerenje mjenjao s geoprostornima. CAD crtež od jedne stranice uredno se učitao, pa je SaveToFile vratio 0 s LastErrorCode 602 i tražio 1.7 ExtensionLevel 3. Ispravljena pravila tretiraju rectilinear /Measure rječnike (/Subtype /RL) kao običan PDF 1.6 i ostavljaju extension gate za stvarne geoprostorne markere

Datoteka je ušla kroz korpusnu recepciju: jedna stranica, jedna optional-content grupa, dva rectilinear measurement viewporta — onakav izlaz kakav piše arhitektonski CAD paket da preglednik može s tlocrta očitati udaljenosti. Ništa u njoj nije bilo egzotično, i upravo zato je odbijanje bilo važno. Preflight koji blokira valjanu datoteku gori je od sporog, jer pozivatelj dobije dijagnostiku koja izgleda autoritativno a upire u značajku koju dokument ne sadrži. Popravak se sastojao od dva dijela: čitanja specifikacije iza jednog pravila, i spoznaje da pravilo na razini na kojoj je gledalo ne može razlikovati dva tipa rječnika

Kako radi version preflight pri spremanju u PDFlibPasu?

Gate pri spremanju, PrepareAndCheckSaveVersion, uspoređuje svaki indirektni objekt s PDFFeatureRules i pada na prvom pravilu koje se i poklopi i traži više nego što cilj dopušta. Cilj je verzija dokumenta (ili verzija prikovana s LockSaveVersion), plus Adobe extension level deklariran pod /Extensions /ADBE. Svaki zapis TPDFFeatureRule nosi MinVersion, MinExtensionLevel, MatchKind poput fmkDictKey ili fmkDictSubtype, Match string, čovjeku čitljivo Feature ime i opcionalni callback. AddRule registrira obično version pravilo; AddExtensionRule uvijek prikove MinVersion na 17 i dodaje extension level na vrh, pa extension pravilo može zadovoljiti samo PDF 1.7 plus pravi /Extensions unos. Kad gate opali, potrebna verzija i ime značajke ostaju spremljeni za pozivatelja, i ključevi 311, 312 i 313 GetInformationa ih izlažu

Version preflight pri spremanju u PDFlibPasu: PrepareAndCheckSaveVersion uspoređuje svaki objekt s PDFFeatureRules zapisima koji nose MinVersion, extension level i match kind, AddExtensionRule prikuje zahtjev na PDF 1.7 plus extension level, a prvo poklapanje koje cilj ne može zadovoljiti zaustavlja spremanje s greškom 602
Ključevi 311, 312 i 313 GetInformationa pretvaraju odbijanje u dijagnozu, javljajući potrebnu verziju, značajku koja ju je okidala i zaključani cilj, pa pozivatelj može popraviti datoteku ili pravilo umjesto da nagađa
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: značajka koja ju je okidala,
        // 313: verzija na koju je save cilj 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 s greškom 602?

Tablica pravila sadržavala je AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), koja je opalila na svakom rječniku koji je samo imao ključ /Measure, a svaki measurement viewport ga ima. /VP polje stranice drži viewport rječnike, svaki viewport upire u svoj measure rječnik kroz /Measure, i match po prisutnosti ključa tu je stao, bez gledanja što je measure rječnik zapravo bio. Feature sken pri učitavanju mogao je zatim podići broj verzije dokumenta na 1.7, ali nikad ne zapisuje /Extensions deklaraciju u ime ulazne datoteke, pa je save gate vidio PDF 1.7 na extension levelu 0 i javio 1.7 ExtensionLevel 3. To odbijanje da izmisli extension deklaraciju namjerno je: biblioteka tiho ne unapređuje ulaznu datoteku da prekrije pravilo koje je krivo

Specifikacija je oko rectilinear slučaja nedvosmislena. Measure rječnici stigli su s PDF 1.6, a ISO 32000-1 §12.9 daje /Subtype zadanu vrijednost RL, rectilinear koordinatni sustav opisan vlastitim skupom unosa: omjer mjerila, formati brojeva X i Y, udaljenost i površina. Geoprostorno mjerenje kasnija je nadogradnja iz Adobe Extension Level 3 na vrhu PDF 1.7, prepoznatljiva po /Subtype /GEO, i nose geografske point arrayeve, rječnike koordinatnih sustava i display jedinice — strukture koje prolazi članak o čitanju GeoPDF viewportova te GPTS i LPTS polja u Delphiju. Oba rječnika vise s istog ključa /Measure, pa nijedno pravilo koje stane na ključ ne može biti pravo za oba. Razlikovna informacija sjedi jednu razinu dublje, u samom measure rječniku

Jedan /Measure ključ, dva rječnika u PDFlibPasu: rectilinear mjerenje, s /RL ili izostavljenim subtypeom, treba samo PDF 1.6, dok geoprostorni rječnik treba 1.7 ExtensionLevel 3, pa CB_GeospatialDictionary odlučuje po sadržaju tamo gdje staro pravilo po ključu nije moglo razlučiti
Preflight koji blokira valjanu datoteku gori je od sporog, jer pozivatelj dobiva autoritativnu dijagnostiku o značajci koju dokument nikad nije sadržavao, pa se razlikovna provjera spustila jednu razinu dublje

Što ispravljeni skup pravila i dalje nameće?

Popravak briše bezuvjetno pravilo po ključu i ostavlja gateove koji opisuju stvarne zahtjeve verzije. Stranica koja nosi /VP ili /UserUnit i dalje treba PDF 1.6 kroz CB_PagePDF16Entries, ključ /PtData i dalje treba extension level 3, a CB_GeospatialDictionary odlučuje je li measure rječnik geoprostoran po svom sadržaju, a ne po ključu kojim je stigao

// Uklonjeno: svaki rječnik s ključem /Measure računao se kao geoprostoran
// 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 prikivaju tu granicu s obje strane. Viewport čiji measure rječnik izostavlja /Subtype i onaj koji izričito piše /RL prolaze oba na PDF 1.6, ista se stranica i dalje odbija na PDF 1.5, i feature detekcija više ne javlja extension za nju. Dodavanje /GPTS polja vraća presudu na 1.7 ExtensionLevel 3, što prolazi jednom kad se extension level deklarira, a gol rječnik /Subtype /GEO bez njega se odbija. Callback je konzervativan po dizajnu: rectilinear rječnik koji nosi i zalutali ključ /GCS ili /PDU tretira se kao geoprostoran, jer ti ključevi u RL modelu nemaju značenje

LockSaveVersion je mjesto gdje ta promjena postaje vidljiva pozivateljima. TPDFlib.LockSaveVersion prima '1.0' do '1.7', za sve ostalo vraća 0, prikove verziju dokumenta i sprječava pozive sa strane pisanja da je tiho podižu, ali save gate i dalje radi protiv zaključane vrijednosti. S ispravljenim pravilima CAD datoteka zaključana na 1.6 sprema se čisto. Pravi GeoPDF zaključan na 1.6 i dalje dobiva 602, što je točan odgovor, a geoprostorni authoring pozivi poput SetMeasureDictCoordinateSystem sami deklariraju 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
    // Stvarni sadržaj iznad 1.6, na primjer GEO measure rječ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 treba?

Sken je svaki TPDFFeatureRule kopirao u lokalni zapis prije testiranja, i budući da zapis drži dva AnsiString polja, svaka je kopija namjestila dva brojača referenci i pustila prethodne vrijednosti. Preflight posjećuje svaki čvor svakog objektnog treea, skalare uključivo, pa se taj trošak množio broj objekata s brojem pravila, i pravila koja se na ciljnu verziju nisu ni odnosila kopirala su se prvo, a preskakala poslije. Budući da se PDFFeatureRules puni jednom pri inicijalizaciji unita i tretira read-only, v3.539.17 unose tablice predaje izravno MatchSingleRule i RuleExceedsTarget, čiji parametri const Rule uzimaju referencu bez diranja stringova

Ubrzanje skena pravila u PDFlibPasu: preflight je prije testiranja kopirao svaki TPDFFeatureRule zapis, namještajući AnsiString brojače referenci za svaki posjećeni objekt, dok const parametri sada čitaju read-only tablicu na mjestu, spustivši medijan kruga matchanja pravila sa 0.711 s na 0.203 s
Dobit je stvarna ali uska: puno spremanje plaća i odgođenu feature detekciju, dekodiranje objekata i serializaciju, pa izmjereni omjer pripada putanji matchanja pravila, a ne ukupnom vremenu spremanja
// Prije: kopija managed zapisa po pravilu, po posjećenom objektu
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
  Continue;

// Poslije: const parametri čitaju nepromjenjivi unos tablice na mjestu
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;

Izmjereni učinak usak je i tako ga i treba citirati. Benchmark provjerava polje od 20.000 brojčanih objekata protiv cilja PDF 1.4 deset puta po krugu; build s FPC Win64 na -O2, medijan pet krugova pao je sa 0.711 s na 0.203 s, a pokretanje dva builda obrnutim redoslijedom dalo je 0.459 s prema 0.150 s. To je grubo rečeno 3x dobit na samoj putanji matchanja pravila. Pravo spremanje plaća i odgođenu feature detekciju, dekodiranje objekata i serializaciju, pa se omjer ne prenosi na ukupno vrijeme spremanja. Redoslijed pravila, callbackovi, pragovi verzija i dijagnostika prvog pada nepromijenjeni su, i nijedno se pravilo nije keširalo preko spremanja ni preskakalo da se dođe dotle

Što provjeriti kad učitani PDF padne version preflight?

Pročitajte ključeve 311 i 312 prije nego dirate verziju. Ako značajka imenuje geoprostorni rječnik, a datoteka crta samo rectilinear mjerenja, to je bio ovaj lažni pozitiv i trenutačni build sprema datoteku nepromijenjenu. Ako je značajka prava, deklarirajte extension ili se zaključajte na verziju koja sadržaj doista ima; podizanje verzije samo da se gate utiša skriva pitanje mogu li nizvodni konzumenti čitati ono što isporučujete. Isti princip ograničenih, dokazima potkrepljenih provjera pogoni i PDF/E-1 author-mode preflight za inženjerske dokumente, gdje CAD crteži susreću standard usklađenosti, a ne broj verzije

Provjere usklađenosti verzije, measure i geoprostorni rječnici te zaključavanje save verzije svi su dio PDF Library for Delphi, PDFlibPas alata za Delphi, C++Builder i Lazarus developere