Teknik Makale

Delphi'de PDF sürüm preflight'ı: Measure RL ve GEO kuralları

PDFlibPas (PDF Library for Delphi), bir dosyayı yazmadan önce her nesneyi bir PDF sürüm kuralı tablosuyla karşılaştırır ve bu PDF sürüm preflight'ı yakın zamana kadar sıradan CAD ölçüm sözlüklerini jeouzaysal sanıyordu. Tek sayfalık bir CAD çizimi sorunsuz yükleniyor, sonra SaveToFile 0 döndürüyor, LastErrorCode 602 oluyor ve 1.7 ExtensionLevel 3 istiyordu. Düzeltilmiş kurallar, doğrusal /Measure sözlüklerini (/Subtype /RL) sıradan PDF 1.6 sayar ve extension kapısını gerçek jeouzaysal işaretlere ayırır

Dosya corpus kabulünden geldi: tek sayfa, tek bir optional-content grubu, iki doğrusal ölçüm viewport'u; bir mimari CAD paketinin, görüntüleyicinin kattan mesafe okuyabilmesi için yazdığı türden çıktı. Hiçbir yanı egzotik değildi; reddin önemi tam olarak buydu. Geçerli bir dosyayı engelleyen preflight, yavaş olandan da kötüdür, çünkü çağıran belgenin içermediği bir özelliği işaret eden, kendinden emin görünen bir tanı alır. Düzeltme iki parça aldı: bir kuralın ardındaki spesifikasyon okuması ve kuralın baktığı düzeyde iki sözlük türünü ayırt edemediğinin fark edilmesi

PDFlibPas save sırasındaki sürüm preflight'ı nasıl çalışır?

Save kapısı olan PrepareAndCheckSaveVersion, her dolaylı nesneyi PDFFeatureRules ile karşılaştırır ve hem eşleşen hem de hedefin izlediğinden fazlasını isteyen ilk kuralda başarısız olur. Hedef, belge sürümü (LockSaveVersion ile sabitlenen sürüm ya da) artı /Extensions /ADBE altında bildirilen Adobe extension seviyesidir. Her TPDFFeatureRule kaydı bir MinVersion, bir MinExtensionLevel, fmkDictKey ya da fmkDictSubtype gibi bir MatchKind, bir Match dizisi, insan-okur bir Feature adı ve isteğe bağlı bir callback taşır. AddRule düz bir sürüm kuralı kaydeder; AddExtensionRule her zaman MinVersionu 17'de sabitler ve üzerine bir extension seviyesi ekler, böylece bir extension kuralı yalnızca PDF 1.7 artı doğru /Extensions girdisiyle tatmin edilebilir. Kapı tetiklendiğinde istenen sürüm ve özellik adı çağıran için saklanır ve GetInformation 311, 312 ve 313 anahtarları onları ortaya koyar

PDFlibPas'ta save sırası sürüm preflight'ı: PrepareAndCheckSaveVersion her nesneyi MinVersion, extension seviyesi ve match türü taşıyan PDFFeatureRules kayıtlarıyla karşılaştırır; AddExtensionRule gereksinimi PDF 1.7 artı extension seviyesine sabitler ve hedefin tatmin edemediği ilk eşleşme save'i 602 hatasıyla durdurur
GetInformation 311, 312 ve 313 anahtarları reddi tanıya çevirir; istenen sürümü, tetikleyen özelliği ve kilitli hedefi bildirir, böylece çağıran tahmin yürütmek yerine dosyayı ya da kuralı düzeltir
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: istenen sürüm, 312: tetikleyen özellik,
        // 313: save hedefinin kilitli olduğu sürüm (kilitsizken '')
        Writeln('Needs ', Pdf.GetInformation(311),
          ' for ', Pdf.GetInformation(312),
          ', locked at [', Pdf.GetInformation(313), ']');
  finally
    Pdf.Free;
  end;
end;

Sıradan bir CAD çizimi 602 hatasıyla neden başarısız oldu?

Kural tablosunda AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil) vardı; bu, salt /Measure anahtarı taşıyan her sözlükte tetikleniyordu ve her ölçüm viewport'u bir tane taşır. Sayfanın /VP dizisi viewport sözlükleri tutar, her viewport ölçüm sözlüğünü /Measure üzerinden işaret eder ve anahtar varlığı eşleşmesi ölçüm sözlüğünün gerçekte ne olduğuna bakmadan orada duruyordu. Yükleme sırasındaki özellik taraması belge sürüm numarasını 1.7'ye çıkarabiliyordu ama hiçbir zaman bir girdi dosyası adına /Extensions bildirimi yazmaz; save kapısı böylece extension seviyesi 0'da PDF 1.7 görüyor ve 1.7 ExtensionLevel 3 bildiriyordu. Uydurma bir extension bildirimi yazmayı reddetmek bilinçlidir: kütüphane, yanlış bir kuralın üstünü örtmek için bir girdi dosyasını sessizce yükseltmez

Spesifikasyon doğrusal vakada açık ve nettir. Measure sözlükleri PDF 1.6'da geldi ve ISO 32000-1 §12.9 /Subtypea varsayılan RL verir; kendi girdi setiyle tanımlanan doğrusal bir koordinat sistemi: ölçek oranı, X ve Y sayı biçimleri, mesafe ve alan. Jeouzaysal ölçüm, PDF 1.7 üzerindeki Adobe Extension Level 3'ün sonradan eklediği şeydir; /Subtype /GEO ile tanınır ve coğrafi nokta dizileri, koordinat sistemi sözlükleri ve gösterim birimleri taşır — Delphi'de GeoPDF viewport'larını, GPTS ve LPTS dizilerini okumak yazısında gezilen yapılar. İki sözlük de aynı /Measure anahtarına asılıdır, dolayısıyla anahtarda duran hiçbir kural ikisi için de doğru olamaz. Ayırt edici bilgi bir seviye aşağıda, ölçüm sözlüğünün kendisinde durur

PDFlibPas'ta tek /Measure anahtarı, iki sözlük: /RL ya da subtype yokluğundaki doğrusal ölçüm yalnızca PDF 1.6 ister; jeouzaysal sözlük 1.7 ExtensionLevel 3 ister, bu yüzden CB_GeospatialDictionary eski anahtar kuralının ayırt edemediği yerde içeriğe bakarak karar verir
Geçerli bir dosyayı engelleyen preflight yavaş olandan da kötüdür; çağıran, belgenin hiç içermediği bir özellik hakkında kendinden emin bir tanı alır, ayırt edici kontrolün bir seviye aşağı inmesinin sebebi budur

Düzeltilmiş kural seti hâlâ neleri dayatır?

Düzeltme koşulsuz anahtar kuralını siler ve gerçek sürüm gereksinimlerini anlatan kapıları bırakır. /VP ya da /UserUnit taşıyan bir sayfa CB_PagePDF16Entries üzerinden PDF 1.6 istemeye devam eder, bir /PtData anahtarı extension seviyesi 3 ister ve CB_GeospatialDictionary, bir ölçüm sözlüğünün jeouzaysal olup olmadığına ona ulaşan anahtara değil içeriğine bakarak karar verir

// Kaldırıldı: /Measure anahtarı taşıyan her sözlük jeouzaysal sayılıyordu
// 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;

Paylaşılan Delphi ve FPC regresyonları o sınırı iki taraftan sabitler. Ölçüm sözlüğü /Subtypeı atlayan bir viewport ile /RL açıkça yazan bir viewport PDF 1.6'da geçer, aynı sayfa PDF 1.5'te yine reddedilir ve özellik tespiti artık onun için extension bildirmez. Bir /GPTS dizisi eklemek kararı 1.7 ExtensionLevel 3e geri çevirir; extension seviyesi bildirildiğinde geçer ve çıplak bir /Subtype /GEO sözlüğü olmadan reddedilir. Callback tasarımdan muhafazakârdır: başıboş bir /GCS ya da /PDU anahtarı da taşıyan doğrusal bir sözlük jeouzaysal sayılır, çünkü o anahtarlar RL modelinde anlam taşımaz

LockSaveVersion, bu değişikliğin çağıranlara göründüğü yerdir. TPDFlib.LockSaveVersion '1.0'dan '1.7'ye kadar kabul eder, başka her şey için 0 döndürür, belge sürümünü sabitler ve yazıcı tarafı çağrıların onu sessizce yükseltmesini durdurur; save kapısı yine de kilitli değere göre çalışır. Düzeltilmiş kurallarla 1.6'da kilitli bir CAD dosyası temiz kaydolur. 1.6'da kilitli gerçek bir GeoPDF yine 602 alır; doğru cevap budur ve SetMeasureDictCoordinateSystem gibi jeouzaysal yazım çağrıları, o içeriği API üzerinden kurduğunuzda extension seviyesi 3'ü kendileri bildirir

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
    // 1.6 üzeri gerçek içerik, örneğin bir GEO ölçüm sözlüğü
    raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
      [string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;

Sürüm kuralı taraması neden gerektiğinden yavaştı?

Tarama, her TPDFFeatureRulei test etmeden önce yerel bir kayda kopyalıyordu; kayıt iki AnsiString alanı tuttuğu için her kopya iki referans sayısını düzeltiyor ve önceki değerleri serbest bırakıyordu. Preflight her nesne ağacının her düğümünü ziyaret eder, skalerler dahil; o maliyet nesne sayısı ile kural sayısını çarpıyordu ve hedef sürüme hiç uygulanmayan kurallar önce kopyalanıp sonra atlanıyordu. PDFFeatureRules birim başlangıcında bir kez doldurulduğu ve salt-okunur işlendiği için v3.539.17, tablo girdilerini doğrudan MatchSingleRule ile RuleExceedsTarget'e geçirir; ikisinin const Rule parametreleri dizelere dokunmadan referans alır

PDFlibPas'ta kural taraması hızlanması: preflight her TPDFFeatureRule kaydını test etmeden önce kopyalıyor, ziyaret edilen her nesne için AnsiString referans sayılarını düzeltiyordu; const parametreler artık salt-okunur tabloyu yerinde okur ve kural eşleştirme turunun medyanı 0,711 s'den 0,203 s'ye iner
Kazanç gerçek ama dardır: tam bir save ayrıca ertelenmiş özellik tespiti, nesne çözümleme ve serileştirme öder; ölçülen oran kural eşleştirme yoluna aittir, toplam save süresine değil
// Önce: kural başına, ziyaret edilen nesne başına yönetilen kayıt kopyası
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
  Continue;

// Sonra: const parametreler değişmez tablo girdisini yerinde okur
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;

Ölçülen etki dardır ve öyle alıntılanmalıdır. Benchmark, 20.000 sayısal nesnelik bir diziyi bir PDF 1.4 hedefine karşı tur başına on kez sınar; -O2 ile FPC Win64'te derlenmiş hâliyle beş turun medyanı 0,711 s'den 0,203 s'ye indi ve iki derlemeyi ters sırada çalıştırmak 0,459 s karşılığında 0,150 s verdi. Bu, kural eşleştirme yolunda tek başına kabaca 3 katlık bir kazançtır. Gerçek bir save ayrıca ertelenmiş özellik tespiti, nesne çözümleme ve serileştirme öder, oran toplam save süresine taşınmaz. Kural sırası, callback'ler, sürüm eşikleri ve ilk başarısızlık tanısı değişmedi; oraya varmak için hiçbir kural save'ler arasında önbelleğe alınmadı ya da atlanmadı

Yüklü bir PDF sürüm preflight'ını geçemediğinde neye bakmalısınız?

Sürüme dokunmadan önce 311 ve 312 anahtarlarını okuyun. Özellik bir jeouzaysal sözlüğü adlandırıyorsa ve dosya yalnızca doğrusal ölçümler çiziyorsa, bu bu yanlış pozitifti ve güncel bir derleme dosyayı değişmeden kaydeder. Özellik gerçekse ya extension'ı bildirin ya da içeriği gerçekten içeren bir sürüme kilitlenin; kapıyı susturmak için sürümü yükseltmek, teslim ettiğinizi sonraki tüketicilerin okuyup okuyamayacağı sorusunu gizler. Sınırlı ve kanıta dayalı kontrol ilkesi, CAD çizimlerinin bir sürüm numarası yerine uygunluk standardıyla karşılaştığı mühendislik belgeleri için PDF/E-1 author-mode preflightında da geçerlidir

Sürüm uygunluk kontrolleri, ölçüm ve jeouzaysal sözlükler ve save sürüm kilitleme, PDF Library for Delphi, Delphi, C++Builder ve Lazarus geliştiricileri için PDFlibPas araç setinin parçalarıdır