Artikel Teknis

Preflight Versi PDF di Delphi: Aturan Measure RL vs GEO

PDFlibPas (PDF Library for Delphi) memeriksa setiap objek terhadap tabel aturan versi PDF sebelum menulis file, dan sampai belakangan preflight versi PDF itu mengira dictionary pengukuran CAD biasa sebagai yang geospatial. Gambar CAD satu halaman termuat dengan baik, lalu SaveToFile mengembalikan 0 dengan LastErrorCode 602 dan menuntut 1.7 ExtensionLevel 3. Aturan yang sudah dikoreksi memperlakukan dictionary /Measure rectilinear (/Subtype /RL) sebagai PDF 1.6 polos dan menyisakan gate ekstensi untuk penanda geospatial yang sungguhan

File itu masuk lewat corpus admission: satu halaman, satu grup optional-content, dua viewport pengukuran rectilinear, jenis output yang ditulis paket CAD arsitektur agar viewer bisa membaca jarak dari denah lantai. Tidak ada yang eksotis darinya, dan justru itu sebabnya penolakannya berarti. Preflight yang memblokir file valid lebih buruk daripada yang lambat, karena pemanggil menerima diagnostik yang tampak otoritatif menunjuk fitur yang tidak dimuat dokumennya. Perbaikannya butuh dua bagian: pembacaan spesifikasi di balik satu aturan, dan kesadaran bahwa aturan itu tak bisa membedakan dua tipe dictionary pada level tempat ia melihat

Bagaimana preflight versi saat simpan PDFlibPas bekerja?

Gate simpannya, PrepareAndCheckSaveVersion, membandingkan setiap objek tak langsung terhadap PDFFeatureRules dan gagal pada aturan pertama yang cocok sekaligus menuntut lebih dari yang diizinkan target. Targetnya adalah versi dokumen (atau versi yang dipatok LockSaveVersion), ditambah level ekstensi Adobe yang dideklarasikan di bawah /Extensions /ADBE. Setiap record TPDFFeatureRule membawa MinVersion, MinExtensionLevel, MatchKind seperti fmkDictKey atau fmkDictSubtype, string Match, nama Feature yang bisa dibaca manusia, dan callback opsional. AddRule mendaftarkan aturan versi polos; AddExtensionRule selalu mematok MinVersion di 17 dan menambahkan level ekstensi di atasnya, jadi aturan ekstensi hanya bisa dipenuhi oleh PDF 1.7 plus entri /Extensions yang tepat. Ketika gate tersandung, versi yang dibutuhkan dan nama fiturnya disimpan untuk pemanggil, dan key GetInformation 311, 312, serta 313 memaparkannya

Preflight versi saat simpan di PDFlibPas: PrepareAndCheckSaveVersion membandingkan setiap objek dengan record PDFFeatureRules yang membawa MinVersion, level ekstensi dan match kind, AddExtensionRule mematok syaratnya di PDF 1.7 plus level ekstensi, dan kecocokan pertama yang tak bisa dipenuhi target menghentikan penyimpanan dengan error 602
Key GetInformation 311, 312 dan 313 mengubah penolakan menjadi diagnosis, melaporkan versi yang dibutuhkan, fitur yang memicunya, dan target yang dipatok, jadi pemanggil bisa membenahi file atau aturannya alih-alih menebak-nebak
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: versi yang dibutuhkan, 312: fitur yang memicunya,
        // 313: versi tempat target simpan dipatok ('' saat tak dipatok)
        Writeln('Needs ', Pdf.GetInformation(311),
          ' for ', Pdf.GetInformation(312),
          ', locked at [', Pdf.GetInformation(313), ']');
  finally
    Pdf.Free;
  end;
end;

Kenapa gambar CAD polos gagal dengan error 602?

Tabel aturannya memuat AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), yang menyala pada setiap dictionary yang sekadar punya key /Measure, dan setiap viewport pengukuran punya satu. Array /VP milik halaman memuat dictionary viewport, setiap viewport menunjuk dictionary measure-nya lewat /Measure, dan kecocokan keberadaan key berhenti di situ tanpa melihat apa sebenarnya dictionary measure itu. Pemindaian fitur saat load lalu bisa menaikkan nomor versi dokumen ke 1.7, tapi ia tidak pernah menulis deklarasi /Extensions atas nama file input, sehingga gate simpan melihat PDF 1.7 pada level ekstensi 0 dan melaporkan 1.7 ExtensionLevel 3. Penolakan mengarang deklarasi ekstensi itu memang disengaja: library tidak diam-diam menaikkan derajat file input untuk menambal aturan yang salah

Spesifikasinya tak ambigu soal kasus rectilinear. Dictionary Measure hadir sejak PDF 1.6, dan ISO 32000-1 §12.9 memberi /Subtype bawaan RL, sistem koordinat rectilinear yang dijelaskan oleh set entri miliknya sendiri: rasio skala, format angka X dan Y, jarak, dan luas. Pengukuran geospatial adalah tambahan belakangan dari Adobe Extension Level 3 di atas PDF 1.7, dikenali dari /Subtype /GEO dan membawa array titik geografis, dictionary sistem koordinat, serta unit tampilan, struktur yang disusuri di membaca viewport GeoPDF beserta array GPTS dan LPTS di Delphi. Kedua dictionary menggantung pada key /Measure yang sama, jadi aturan apa pun yang berhenti di key tak mungkin benar untuk keduanya. Informasi pembedanya ada satu level di bawah, di dictionary measure itu sendiri

Satu key /Measure, dua dictionary di PDFlibPas: pengukuran rectilinear, dengan /RL atau tanpa subtype, hanya butuh PDF 1.6, sementara dictionary geospatial butuh 1.7 ExtensionLevel 3, jadi CB_GeospatialDictionary memutuskan lewat isinya di tempat aturan key lama tak bisa membedakannya
Preflight yang memblokir file valid lebih buruk daripada yang lambat, karena pemanggil menerima diagnostik otoritatif atas fitur yang tak pernah dimuat dokumennya, dan itulah kenapa pemeriksaan pembedanya turun satu level

Apa yang masih ditegakkan oleh set aturan yang dikoreksi?

Perbaikannya menghapus aturan key tanpa syarat itu dan membiarkan gate-gate yang menggambarkan syarat versi sungguhan. Halaman yang membawa /VP atau /UserUnit tetap butuh PDF 1.6 lewat CB_PagePDF16Entries, key /PtData tetap butuh level ekstensi 3, dan CB_GeospatialDictionary memutuskan apakah sebuah dictionary measure geospatial lewat isinya, bukan lewat key yang mengantarnya

// Dihapus: setiap dictionary dengan key /Measure dihitung 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;

Regresi Delphi dan FPC bersama mematok batas itu dari kedua sisinya. Viewport yang dictionary measure-nya menghilangkan /Subtype dan yang mengejakan /RL sama-sama lolos di PDF 1.6, halaman yang sama tetap ditolak di PDF 1.5, dan deteksi fitur tak lagi melaporkan ekstensi untuknya. Menambahkan array /GPTS membalik vonisnya kembali ke 1.7 ExtensionLevel 3, yang lolos begitu level ekstensinya dideklarasikan, dan dictionary /Subtype /GEO polos ditolak tanpa itu. Callback-nya konservatif memang oleh desain: dictionary rectilinear yang juga membawa key nyasar /GCS atau /PDU diperlakukan geospatial, karena key-key itu tak bermakna di model RL

LockSaveVersion adalah tempat perubahan ini terlihat oleh pemanggil. TPDFlib.LockSaveVersion menerima '1.0' sampai '1.7', mengembalikan 0 untuk apa pun di luar itu, mematok versi dokumen, dan menghentikan panggilan sisi penulis menaikkaninya diam-diam, namun gate simpan tetap berjalan terhadap nilai yang dipatok. Dengan aturan yang sudah dikoreksi, file CAD yang dipatok di 1.6 tersimpan mulus. GeoPDF sungguhan yang dipatok di 1.6 tetap kebagian 602, dan itu jawaban yang benar, sementara panggilan authoring geospatial seperti SetMeasureDictCoordinateSystem mendeklarasikan level ekstensi 3 sendiri ketika Anda membangun konten itu lewat 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
    // Konten nyata di atas 1.6, misalnya dictionary measure GEO
    raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
      [string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;

Kenapa pemindaian aturan versi lebih lambat dari seharusnya?

Pemindaiannya menyalin setiap TPDFFeatureRule ke record lokal sebelum mengujinya, dan karena record itu memuat dua field AnsiString, setiap salinan menyetel dua reference count dan melepas nilai sebelumnya. Preflight mengunjungi setiap node dari setiap object tree, termasuk skalar, jadi biaya itu mengalikan jumlah objek dengan jumlah aturan, dan aturan yang bahkan tak berlaku untuk versi target ikut tersalin lebih dulu lalu dilewati. Karena PDFFeatureRules diisi sekali saat inisialisasi unit dan diperlakukan read-only, v3.539.17 meneruskan entri tabel langsung ke MatchSingleRule dan RuleExceedsTarget, yang parameter const Rule-nya mengambil referensi tanpa menyentuh stringnya

Percepatan pemindaian aturan di PDFlibPas: preflight dulu menyalin setiap record TPDFFeatureRule sebelum mengujinya, menyetel reference count AnsiString untuk setiap objek yang dikunjungi, sementara parameter const kini membaca tabel read-only di tempatnya, memangkas median ronde pencocokan aturan dari 0,711 s ke 0,203 s
Kenaikannya nyata tapi sempit: satu penyimpanan penuh juga membayar deteksi fitur tertunda, dekode objek, dan serialisasi, jadi rasio terukurnya milik jalur pencocokan aturan, bukan milik total waktu simpan
// Sebelum: satu salinan record terkelola per aturan, per objek yang dikunjungi
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
  Continue;

// Sesudah: parameter const membaca entri tabel imutabel di tempatnya
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;

Efek terukurnya sempit dan sebaiknya dikutip seadanya. Benchmark memeriksa array 20.000 objek numerik terhadap target PDF 1.4 sepuluh kali per ronde; dibangun dengan FPC Win64 pada -O2, median lima ronde turun dari 0,711 s ke 0,203 s, dan menjalankan kedua build dalam urutan terbalik memberi 0,459 s lawan 0,150 s. Itu kira-kira kenaikan 3x pada jalur pencocokan aturan saja. Satu penyimpanan nyata juga membayar deteksi fitur tertunda, dekode objek, dan serialisasi, jadi rasionalnya tidak terbawa ke total waktu simpan. Urutan aturan, callback, ambang versi, dan diagnostik kegagalan pertama tidak berubah, dan tak ada aturan yang di-cache lintas penyimpanan atau dilewati demi mencapainya

Apa yang perlu Anda periksa saat PDF termuat gagal preflight versi?

Baca key 311 dan 312 sebelum menyentuh versinya. Kalau fiturnya menamai dictionary geospatial dan file-nya hanya menggambar pengukuran rectilinear, itu false positive ini, dan build terkini menyimpan filenya tanpa perubahan. Kalau fiturnya sungguhan, deklarasikan ekstensinya atau patok ke versi yang jujur memuat kontennya; menaikkan versi sekadar membungkam gate menyembunyikan pertanyaan apakah konsumen hilir bisa membaca apa yang Anda kirim. Prinsip yang sama, pemeriksaan berbatas dan berbasis bukti, menggerakkan preflight mode author PDF/E-1 untuk dokumen teknik, tempat gambar CAD bertemu standar kesesuaian alih-alih sekadar nomor versi

Pemeriksaan kepatuhan versi, dictionary pengukuran dan geospatial, serta penguncian versi simpan semuanya bagian dari PDF Library for Delphi, toolkit PDFlibPas untuk pengembang Delphi, C++Builder, dan Lazarus