Delphi ve C++Builder için yerel VCL PDF bileşen kütüphanesi olan PDFlibPas, bir sayfanın içerik akışını, hiçbir render tuvaline dokunmadan tamamen TPDFContentStateTracker sınıfı aracılığıyla yeniden oynatır. Takipçiyi seferinde bir ayrıştırılmış operatörle beslemek, sürekli bir grafik-durumu kaydını -mevcut dönüşüm matrisi, metin matrisi, kırpma sınırları ve q/Q kaydetme yığını- her operatör çalışmadan önce veya sonra anlık görüntü alınabilir halde tutar
Basılı sayfada bir metin çalışmasının gerçekte nereye indiğini sorun ve içerik akışının ham sayıları tek başına sizi her seferinde yanıltacaktır. TPDFContentProgram.GetTextRuns, zaten her metin gösteren talimatın çapa noktasını TPDFTextRun üzerindeki OriginX ve OriginY alanları aracılığıyla bildirir ve alan yorumları bu noktanın metin uzayında oturduğunu, zaten Tm, Td, TD ve T* üzerinden katlandığını açıkça belirtir. Hâlâ eksik olan ve bu yorumların bir çağıranın sağlaması gerektiğini söylediği şey, o tam talimatta aktif olan CTM'dir -akışta o noktada kaç q/Q çifti açık olursa olsun, onların içine yerleştirilmiş, şimdiye kadar birleştirilen her cm'nin çarpımı
Neden bir içerik akışını render etmek yerine yeniden oynatmalı?
PDFlibPas, iki ayrı iş için iki ayrı grafik durumu kavramı tutar ve bu ayrım kasıtlıdır. Render motorunun dahili durum kaydı, canlı bir cihaz-tuvali tutamacı, bir kırpma-bölgesi tutamacı ve yazı tipi rasterleştirme önbellekleri taşır -o anda boyanmakta olan yüzeye bağlı gerçek kaynaklar ve o yüzey kaybolduğunda anlamsız hale gelir. TPDFContentGraphicsState bunların hiçbirini taşımaz: ISO 32000-1 §8.4'ün yalnızca içerik akışı operatörlerinden ulaşılabilir olarak tanımladığı değerlerle sınırlı düz bir kayıttır -CTM, çizgi stili, renk, metin durumu ve türetilmiş kırpma ve yol sınırları. Kayıt hiçbir tuval referansı ve hiçbir açık dosya tutamacı taşımadığından, bir çağıran bir içerik akışını ayrıştırabilir, onu TPDFContentStateTracker ile dolaşabilir ve baytları üreten her ne kaybolduktan uzun süre sonra bile ortaya çıkan anlık görüntüleri kullanmaya devam edebilir
TPDFContentStateTracker CTM'yi nasıl kurar?
TPDFContentStateTracker.Apply, bir cm operatörünün altı işlenenini, PDF'in kendisinin belirttiği aynı ön-çarpma ile takipçinin CTM'sine birleştirir: yeni matris M2, mevcut CTM ile M2 × CTM olarak birleşir, bir noktanın P′ = P × M olarak dönüştüğü satır-vektör kuralında (ISO 32000-1 §8.4). Yanlış anlaşılması kolay olan kısım, doğrusal kısımda değil öteleme teriminde oturur: M2'nin kendi ötelemesi, mevcut CTM'nin ötelemesi üste eklenmeden önce mevcut CTM'nin döndürme-ve-ölçek bileşeninden geçmek zorundadır. Bu adımı atlayıp bunun yerine naif bir bileşen bazlı birleştirmeyi sabit kodlayın ve test ettiğiniz ilk yalıtılmış cm doğru görünecektir, ama ikinci veya üçüncü iç içe geçmiş bir cm'nin aşağı akışındaki her koordinat sessizce kayacaktır ki bu, kod incelemesinden sağ çıkan tam olarak bu türden bir hatadır, çünkü onu yakalayacak birim testinin başarısız olması için en az iki zincirlenmiş dönüşüme ihtiyacı vardır
var
Prog: TPDFContentProgram;
Runs: TPDFTextRunArray;
States: TPDFContentGraphicsStateArray;
DeviceX, DeviceY: Double;
I: Integer;
begin
Prog := TPDFContentProgram.Create;
try
Prog.Parse(ContentBytes);
Runs := Prog.GetTextRuns;
// One before-instruction snapshot per operator, computed in a single pass
States := Prog.TraceGraphicsStates(nil, False);
for I := 0 to High(Runs) do
begin
// OriginX/OriginY already fold in Tm/Td/TD/T*; only the CTM active
// at this instruction is still missing (ISO 32000-1 8.4)
with States[Runs[I].InstructionIndex].CTM do
begin
DeviceX := Runs[I].OriginX * M11 + Runs[I].OriginY * M21 + DX;
DeviceY := Runs[I].OriginX * M12 + Runs[I].OriginY * M22 + DY;
end;
LogTextOrigin(Runs[I].Text, DeviceX, DeviceY); // caller-supplied handler
end;
finally
Prog.Free;
end;
end;
Yukarıdaki döngü, girişteki sıkıntılı noktayı yanıtlar: TPDFContentProgram.GetTextRuns, zaten Tm, Td, TD ve T* üzerinden katlanmış OriginX ve OriginY'yi geri verir ve TraceGraphicsStates(nil, False), kalan tek parçayı, her çalışmanın yakalandığı tam indeksteki talimat-öncesi CTM'yi, tüm program üzerinde tek bir doğrusal geçişte sağlar. nil geçirmek, metodun çağrı için özel bir takipçiye sahip olmasına ve onu dahili olarak serbest bırakmasına izin verir ki bu, tek seferlik bir tarama için doğru seçimdir; mevcut bir TPDFContentStateTracker örneğini geçirmek ise, ISO 32000-1 bir sayfanın /Contents dizisini tek bir mantıksal akış olarak ele aldığından ve q/Q yığınının uyuşması gerektiğinden, birden fazla içerik akışından kurulu bir sayfa genelinde durumu sürekli tutan şeydir
Metin matrisi Q'dan sağ çıkar; grafik durumu çıkmaz
ISO 32000-1 §9.4.2, Td, TD, Tm ve T*'yi bir BT/ET bloğu içinde metin matrisini ve metin satır matrisini kuran operatörler olarak tanımlar ve PDFlibPas bu ayrımı keskin tutar: Td ve TD, metin satır matrisine saf bir ötelemeyi birleştirir, T* aynısını mevcut aralığın negatifini kullanarak yapar ve yalnızca Tm, her ikisini de kendisine verilen altı sayıyla tamamen değiştirir. BT, metin nesnesinin başında, tam olarak bir kez, her iki matrisi de birim matrise sıfırlar -ama q ve Q onlara hiç dokunmaz. TPDFContentStateTracker.Apply, tam olarak bu nedenle coRestoreState'i özel bir durum olarak ele alır: kaydedilmiş durumu yığından çıkarmadan önce, mevcut metin matrisini, metin satır matrisini ve BT/ET bayrağını yakalar ve çıkarılan durumun ne tutuyorsa onun üzerine yeniden uygular, çünkü bir metin çalışmasının etrafını saran bir q/Q çifti, metin konumunu geri taşımaması gerekir
var
Tracker: TPDFContentStateTracker;
Prog: TPDFContentProgram;
I: Integer;
begin
Prog := TPDFContentProgram.Create;
Tracker := TPDFContentStateTracker.Create;
try
Prog.Parse('BT 100 700 Td q 2 0 0 2 0 0 cm (A) Tj Q (B) Tj ET');
for I := 0 to Prog.Count - 1 do
begin
Tracker.Apply(Prog[I]);
if Prog[I].Op in [coShowText, coRestoreState] then
LogState(Prog[I].OpName, Tracker.Snapshot); // caller-supplied handler
end;
finally
Tracker.Free;
Prog.Free;
end;
end;
Bu sırayı çalıştırın ve ikinci Tj'de bildirilen CTM, q'dan önce sahip olduğu birim ölçeğine geri döner -kaydetme/geri yükleme çifti içindeki 2 0 0 2 0 0 cm, q/Q'nun gerektirdiği gibi kaybolmuştur. Aynı talimattaki TextMatrix.DX ise hâlâ 100'dür: onu ayarlayan Td, q'dan önce çalıştı; bu yüzden Q'nun asla dokunmaya hakkı olmadığı bir grafik durumu değildir ve aksini varsayan bir araç, ikinci glif çalışmasının sayfada yanlış yatay konumdan başladığını bildirirdi
Bir kırpma yolu operatörü çalıştığında ne olur?
Bir W veya W* operatörü kırpmayı hemen küçültmez; yalnızca hangi doldurma kuralının kullanılacağını kaydeder ve gerçek kesişim, PDF yazarlarının hiçbir şey çizmeden tam olarak kırpmak için rutin olarak kullandığı no-op boyayıcısı n dahil, onu izleyen hangi yol-boyama operatörü varsa onu bekler. TPDFContentStateTracker, bu iki adımlı zamanlamayı tam olarak yansıtır: coClip ve coClipEvenOdd yalnızca bekleyen bir kırpma-kuralı bayrağını ayarlar ve her yol-boyama operatörü tarafından çağrılan EndCurrentPath, bekleyen yolun sınırlarını ClipMinX, ClipMinY, ClipMaxX ve ClipMaxY'ye gerçekten kesiştiren şeydir. Bu aşamalamayı doğru yapmak, önce/sonra anlık görüntü sözleşmesinin kendisi için önemlidir: tam olarak W talimatında alınan bir önce-anlık görüntüsü, akışta o noktada kırpma henüz etkiye girmediğinden, hâlâ eski, daha geniş kırpmayı göstermek zorundadır ve iki adımı bire indirmek, önce-durumun söylediği anlama geldiğine güvenen her çağıranı sessizce bozardı
ClipBoundsExact, bir çağırana iki durumdan hangisine baktığını söyler ve yalnızca aksi halde boş bir yol üzerinde re tarafından kurulan tek bir eksen-hizalı dikdörtgen için True olur -PDFlibPas'ın tam olarak dört sayı olarak temsil edebileceği tek şekil. Diğer her şey -döndürülmüş bir dikdörtgen, kavisli bir dış hat, birkaç alt-yola sahip bileşik bir yol veya bir metin-render modundan kurulu bir kırpma- yine de ClipMinX'ten ClipMaxY'ye kadar üretir, ama ClipBoundsExact False'a temizlenmiş olarak; dört sayının güvenli bir dış sınır olduğunun, gerçek kırpma şekli olmadığının dürüst bir sinyali. Yalnızca o sınıra ihtiyaç duyan çağıranlar -PDF sayfalarını 1-bit monokrom render etme makalesinde açıklanan GDI yarı ton aşağı-dönüştürmeden önce dikdörtgen bir alt bölgeyi yalıtmak gibi- onu sayfa geometrisinden yeniden türetmek yerine doğrudan okuyabilir
Bézier eğrileri: kesin bir sınır ya da güvenli bir sınır
Kübik bir Bézier segmentini sınırlamanın en ucuz yolu, dört kontrol noktasının dışbükey gövdesini almaktır ve bu her zaman güvenlidir çünkü eğri onu asla terk etmez -ama sığ, geniş bir eğri, gerçekte kapladığından çok daha büyük bir sınırlayıcı kutu bildirebilir ki bu, kırpma tabanlı filtrelemeyi tam olarak en çok önem verdiği yerde, büyük dekoratif yollarda zayıflatır. PDFlibPas bunun yerine daha sıkı sorunu çözer: her eksen için, kübik eğrinin türevini açık aralık (0, 1) içindeki kökler için çözer ve bulduğu köklerde, her iki uç noktayla birlikte, eğriyi değerlendirir ki bu, bir eğrinin gerçek eksen-hizalı boyunu, bir aşırı tahmin yerine elde etmenin standart kapalı-form yoludur. Ancak eğri başına hassasiyet, kırpmanın kendisine taşınmaz: kavisli bir dış hat bir kırpma yolu haline geldiğinde, ClipBoundsExact onun için yine de False'a düşer, çünkü ne kadar sıkı olursa olsun bir sınırlayıcı kutu, yine de sınırladığı eğriyle aynı şekil değildir ve durum takipçisi, bir çağıranın gerçekte bir eğri olan yerde bir dikdörtgen varsaymasına izin vermektense bunu söylemeyi tercih eder
Her operatörden önce ve sonra durumu okumak
Bir çağıranın önce mi sonra mı durumu istediği tamamen operatörün ne yaptığına bağlıdır: bir yol veya metin çalışması hakkında bir çizim veya isabet-testi sorusu, o operatörün onu boyama şeklini gerçekte belirleyen şey olduğundan, operatör çalışmadan önceki an durumu ister, oysa gs gibi durum-ayarlayan bir operatör hakkında bir tanılama sorusu genellikle onun az önce neyi değiştirdiğini görmek ister. TPDFContentProgram.TraceGraphicsStates(Tracker, AfterInstruction), tam olarak bu seçimi tek bir Boolean olarak sunar; hangi anın istendiğine bakılmaksızın, tüm program üzerinde tek bir doğrusal geçişte talimat başına bir TPDFContentGraphicsState hesaplar. GetGraphicsState(InstructionIndex, AfterInstruction, State), tüm program yerine tek bir talimat için aynı önce/sonra seçimini sunar, ama her çağrıda oraya ulaşmak için sıfır talimatından yeniden oynatır; bu yüzden onu bir döngüde çağırarak birçok indeksi taramak, aynı program üzerinde tek bir O(n) çağrısına karşı O(n²) tutar
var
Before, After: TPDFContentGraphicsState;
begin
// Same instruction index, two different instants: before vs. after it runs
Prog.GetGraphicsState(CmIndex, False, Before);
Prog.GetGraphicsState(CmIndex, True, After);
// Before.CTM reflects every earlier cm; After.CTM already folds in
// this instruction's own concatenation as well
end;
Bozuk içerik akışlarıyla yaşamak
Gerçek PDF üreticilerinde iki tür bozuk girdi, TPDFContentStateTracker'ın onlara başarısız olmak yerine tolerans göstermesi gerekecek kadar yaygındır. Birincisi, bir q/Q sınırını kapsayan bir yoldur: mevcut yol, mevcut nokta ve alt-yol sayısı grafik-durumu parametreleri değildir -ISO 32000-1 §8.4, q ve Q'nun neyi kaydedip geri yüklediğini kapsar ve kurulmakta olan mevcut yol bunların arasında değildir- bu yüzden TPDFContentStateTracker o veriyi kaydedilmiş durumun tamamen dışında takip eder ve bir q'dan önce başlayan bir alt-yol, eşleşen Q'dan hemen sonra bile hâlâ oradadır, boyanmamış olarak. İkincisi, akışta ondan önce hiçbir yerde eşleşen bir q'su olmayan çıplak bir Q'dur; içerik akışı parçalarını birleştirerek kuran ve muhasebeyi yanlış yapan üreticilerin çıktısında nadir değildir. TPDFContentStateTracker.RestoreUnderflowCount, bir istisna fırlatmak veya durumu bozmak yerine bu olayların her birini sayar: eşleşmeyen bir Q, mevcut grafik durumunu tam olarak olduğu gibi bırakır, sanki o talimat bir no-op'muş gibi; bu yüzden akışın geri kalanı makul bir durum üzerinde yeniden oynatmaya devam eder ve bir çağıran sonradan, sayıdan, girdinin onu üretene geri işaretlenmeye değer olup olmadığına yine de karar verebilir
CTM birleşimi, metin matrisinin q/Q'dan bağımsızlığı ve bir kırpma yolunun aşamalı gerçekleşmesi, içerik akışının hiç boyanıp boyanmadığına veya nasıl boyandığına bağlı değildir ki tam olarak mesele budur: aynı TPDFContentStateTracker anlık görüntüsü, sayfa hiç render edilmese de veya PDFlibPas'ın o dosya için seçtiği hangi arka uca teslim edilmek üzere olsa da doğrudur, PDFlibPas'ta çok motorlu PDF render rehberinde ele alınan çalışma zamanı motor geçişi dahil. İçerik analizi, koordinat eşleme ve karartma araçlarının hepsi, bir render motorunun hiç dahil olmasını istemeden çok önce veya hiç istemeden, tamamen takipçinin çıktısı üzerinde çalışabilir
TPDFContentStateTracker aracılığıyla içerik akışı yeniden oynatma, Delphi ve C++Builder için yerel VCL PDF bileşen kütüphanesi olan PDFlibPas'a yerleşik yapılandırılmış içerik düzenleme çerçevesinin bir parçasıdır