Çoğu PDF sayfası birkaç milisaniye içinde rasterleştirilir (rasterises) ve bunu hiç düşünmezsiniz bile (never think about it). Sonra bir kullanıcı bir A1 mühendislik çizimini, on binlerce (tens of thousands) vektör (vector) darbesiyle (strokes) dolu bir sayfayı veya saydamlık grupları (transparency groups) ve yumuşak maskelerle (soft masks) kalabalıklaşmış (crowded) bir posteri (poster) açar ve onu (it) çizen (paints) o tek (single) çağrı iki (two) veya üç saniye (seconds) sürer. Bu çağrı UI iş parçacığında çalışırsa pencere yeniden boyamayı (repainting) bırakır, başlık çubuğu (title bar) kararır (greys out) ve işletim sistemi (operating system) uygulamayı (application) sonlandırmayı (kill) teklif eder (offers). İş (work) meşrudur (legitimate). Sayfanın (page) gerçekten de (really does) o kadar zamana (long) ihtiyacı vardır (need). Buradaki kusur (defect), oluşturmanın (render), nefes almak için yukarı çıkmanın bir yolu (no way to come up for air) ve durmanın bir yolu olmayan, bölünemez (indivisible) engelleyici (blocking) bir çağrı olmasıdır (is one indivisible blocking call)
Bu makale tam olarak bu iki sorundan biriyle ilgilidir: UI'yi dondurmadan uzun (long) tek sayfalık (single-page) bir oluşturmayı (render) iptal etmek. Kullanıcı (user) sonraki sayfaya tıkladı, yakınlaştırdı (zoomed) veya belgeyi (document) kapattı ve devam eden oluşturma (render in flight) artık (now) sonuna kadar çalışmak yerine (instead of run to completion) bir sonraki fırsatta (next opportunity) bitmesi (end) gereken (should) boşa harcanmış (wasted) bir iş (work). Zaten rasterleştirilmiş olanı önbelleğe (caching) alarak kaydırmayı (scroll) ve yakınlaştırmayı (zoom) yumuşatmak (Smoothing), sonunda bağlantısı verilen (linked at the end) eşlik eden makalede (companion article) ele alınan kendi tasarımıyla (its own design) ayrı (separate) bir endişedir (concern). Burada (here) tek soru (question), tek bir aşamalı (progressive) oluşturmanın (render) bir iptal etme (cancel) talebine (request) hızlı (quickly) ve temiz bir şekilde (cleanly) yanıt vermesini (answer) nasıl sağlayacağımızdır
PDFium'un hali hazırda (already) gönderdiği (ships) aşamalı oluşturma (progressive render) API'si
PDFium sorunun donma (freezing) yarısını (half) öngördü (anticipated). Tek seferlik (one-shot) FPDF_RenderPageBitmap'in yanı sıra, sayfayı (page) iş parçalarına (chunks of work) bölen (splits) aşamalı (progressive) bir varyantı (variant) ortaya çıkarır (exposes). Hedef bitmap'e (destination bitmap) karşı oluşturmayı ayarlamak (set up) için bir kez FPDF_RenderPageBitmap_Start çağrısı (call) yaparsınız (You call), ardından (then) art arda (repeatedly) FPDF_RenderPage_Continue çağrısı (call) yaparsınız. Her Continue sınırlı bir dilimi (bounded slice) rasterleştirir ve bir durum (status) döndürür (returns). FPDF_RENDER_TOBECONTINUED yapacak daha çok iş (more to do) olduğu anlamına gelir, FPDF_RENDER_DONE sayfanın (page) bittiği (finished) anlamına gelir ve FPDF_RENDER_FAILED bir hata (error) nedeniyle durduğu anlamına gelir. Döngü bittiğinde, sayfa başına aşamalı durumu (per-page progressive state) serbest bırakmak için FPDF_RenderPage_Close'u çağırırsınız (you call). Dilimler arasında kontrol kodunuza döndüğünden, iletileri (messages) pompalayabilir (pump), bir ilerleme göstergesini (progress indicator) güncelleyebilir veya işin (work) hala (still) istenip istenmediğini kontrol edebilirsiniz (check)
PDFium'un ne zaman geri adım atılacağına (yield) karar vermek için sağladığı mekanizma (mechanism) IFSDK_PAUSE adında bir geri çağırma yapısıdır (callback struct). Onu (it) Start'a ve her Continue'ya devredersiniz (hand). Her parçadan (chunk) sonra PDFium kendi (its) NeedToPauseNow fonksiyon (function) göstericisini (pointer) çağırır ve bu (that) sıfır olmayan (non-zero) bir değer döndürürse (returns), mevcut Continue erken durur (stops early) ve kontrolü (control) FPDF_RENDER_TOBECONTINUED ile geri (back) verir (hands). Yapı ayrıca, 1'e ayarlanması (set) gereken bir version alanı (field) ve PDFium'un asla dokunmadığı (never touches) ve dokunulmadan (untouched) geçirdiği serbest biçimli (free-form) bir user göstericisi (pointer) taşır (carries). Bu dokunulmamış gösterici, izleyen tasarımın tam menteşesidir (whole hinge)
Duraklatmayı iptal etme (cancel) olarak yeniden tasarlama (Repurposing pause as cancel)
NeedToPauseNow'un orijinal (original) amacı (intent) zaman dilimlemedir (time-slicing). Kare bütçeniz (frame budget) harcandığında sıfır olmayan (non-zero) bir değer döndürün (return), oluşturmaya (rendering) devam etmek için sıfır döndürün (return zero) ve aynı (same) oluşturmayı (render) sürdürmeden önce (before resuming) başka bir şey (something else) yapabilmeniz (can do) için PDFium duraklar (pauses). PDFium Bileşeni (Component) aynı sinyali farklı (different) bir fiil (verb) için yeniden kullanır (reuses). "Duraklayıp devam etmenize izin vermeli miyim" ("should I pause and let you resume") sorusunu (answering) yanıtlamak yerine, geri çağırma "bu iş iptal edildi mi" ("has this work been cancelled") sorusunu (answers) yanıtlar. İkisi, bayrağı (flag) gördüğünde (sees) döngünün (loop) ne yaptığı (does) nedeniyle birbiriyle temiz (cleanly) bir şekilde (cleanly) eşleşir (map). Gerçek bir duraklama (genuine pause) daha sonra (later) bir Continue bekler; bir iptal (cancel) ise beklemez (does not). Çağıran döngü (calling loop), belirtecin (token) iptal (cancelled) edildiğini (that) gözlemledikten sonra (observes), oluşturma bağlamını (render context) kapatır ve (and) bir daha (again) asla (never) Continue işlevini çağırmaz, böylece PDFium'un "bu parçayı durdur" ("stop this chunk") olarak (as) okuduğu aynı sıfır olmayan (non-zero) dönüş (return), fiilen (in effect), "temelli durdur" ("stop for good") haline gelir
İptal etme (Cancellation), IsCancelled özelliği programın başka bir bölümü (part) oluşturmanın (render) durmasını (stop) istediğinde false (yanlış) değerinden (from false) true (doğru) değerine (to true) dönen bir arayüz (interface) olan IPdfCancellationToken ile ifade edilir (expressed through). Bu Pascal arayüzü ile PDFium'un C geri çağırması arasındaki köprü (bridge) tek (single) bir göstericidir (pointer). Belirtecin arayüz (interface) referansı IFSDK_PAUSE.user içine yazılır ve statik bir cdecl geri çağırması onu tekrar okur (reads it back out) ve sorgular (queries it). Bu (This), bir C kütüphanesinin (library) Pascal'a geri dönmesine (call back) izin vermenin (letting) klasik (classic) sorunudur (problem): geri çağırma (callback), bir yöntem (method) değil (not a method), C çağırma kuralına (calling convention) sahip (with) düz (plain) bir fonksiyon (function) olmalıdır, çünkü PDFium Pascal nesneleri (objects) veya Self hakkında hiçbir şey (nothing) bilmeyen (knows) çıplak (bare) bir fonksiyon göstericisini saklar (stores) ve çağırır (invokes)
type
TPdfProgressivePause = record
Pause: IFSDK_PAUSE; // PDFium reads this; .user holds the token
Token: IPdfCancellationToken; // strong ref keeps the token alive
end;
function ProgressivePauseCallback(pThis: PIFSDK_PAUSE): FPDF_BOOL; cdecl;
var
Token: IPdfCancellationToken;
begin
Result := 0;
if (pThis = nil) or (pThis^.user = nil) then
Exit;
Token := IPdfCancellationToken(pThis^.user);
if Token.IsCancelled then
Result := 1; // non-zero: PDFium stops this chunk
end;
Geri çağırma, pThis^.user göstericisini arayüz türüne geri (back) çevirerek (casting) belirteci kurtarır (recovers) ve IsCancelled durumunu (IsCancelled) okur (reads). İçindeki hiçbir şey (Nothing in it) ayırmaz (allocates), kilitlemez (locks) veya engellemez (blocks), ki bu (which) önemlidir çünkü PDFium bunu (it) her (every) parçadan (chunk) sonra oluşturma (rendering) iş parçacığında çağırır (calls) ve burada yapılan (done) herhangi bir iş (any work) oluşturmanın (render) kendi (itself) maliyetine (cost) eklenir. Nil (boş) bir yapıya (struct) veya nil (boş) bir user alanına karşı olan bu koruma (guard), gerçek bir belirteç (token) verilmemiş (was never given) bir oluşturmada bile aynı (same) işlevi (function) yüklemenin (to install) güvenli olduğu anlamına gelir
Döngü (loop) boyunca belirteci (token) canlı tutmak (Keeping alive)
Bir arayüz (interface) göstericisini (pointer) ham (raw) bir Pointer üzerinden (through) ve geri (back) çevirmek (Casting), yaşam döngüsü (lifetime) hatalarının (bugs) doğduğu yerdir. Delphi'de bir IInterface referans sayımlıdır (reference counted), ve (and) sayım (count) yalnızca (only) derleyici (compiler), arayüz tipli (interface-typed) bir değişkenin (variable) atandığını (being assigned) gördüğünde (can see) hareket eder (moves). Belirteci (token) yalnızca (solely) IFSDK_PAUSE.user içinde çıplak (bare) bir gösterici olarak saklamak, onu (it) referans sayacından (reference counter) tamamen (completely) gizlerdi. Bu belirtece yönelik diğer (other) tek (only) referans (reference), Continue döngüsü hala (still) çalışırken (running) kapsamın dışına (out of scope) çıkarsa, nesne (object) geri çağırmanın altında (underneath) serbest (freed) bırakılır ve (and) sonraki parça (chunk) sarkan bir göstericinin (dangling pointer) başvurusunu (dereference) kaldırırdı
Tanımlayıcının (descriptor) bir (one) değil (not), iki (two) şey (things) tutan bir kayıt (record) olmasının (is) nedeni budur (That is why). Pause alanı, PDFium'un okuduğu yapıdır. Token alanı derleyicinin saydığı (counts) gerçek (real) arayüz tipi (interface-typed) bir referanstır (reference) ve (and) belirteci (token) kayıt (record) yaşadığı (lives) sürece (for as long as) belleğe sabitlemek (pin) dışında hiçbir nedenle (for no other reason) mevcut değildir (exists). Kayıt (record), oluşturma alt yordamının (render routine) yığınında (stack) bulunan yerel (local) bir değişkendir (variable), dolayısıyla (so) döngünün (loop) tüm (entire) süresi (duration) boyunca (for) geçerli kalır (stays valid) ve (and) yalnızca (only) alt yordam çıkış (exits) yaptığında (when) yok edilir (torn down). user içindeki çıplak (bare) gösterici (pointer) ile Token içindeki sayılan (counted) referans (reference) aynı (same) nesneyi (object) adlandırır; biri PDFium'un okuyabildiği (can read) şeydir, diğeri (the other) o (that) nesnenin (object) toplanmasını (being collected) engelleyen (keeps) şeydir
var
Pause: TPdfProgressivePause;
EffectiveToken: IPdfCancellationToken;
begin
// ... choose EffectiveToken ...
// Strong ref first, then publish the same object to PDFium via .user.
Pause.Token := EffectiveToken;
Pause.Pause.version := 1;
Pause.Pause.NeedToPauseNow := ProgressivePauseCallback;
Pause.Pause.user := Pointer(EffectiveToken);
Döngü (loop) nasıl (how) biterse (ends) bitsin oluşturma bağlamını (render context) kapatmak (Closing)
FPDF_RenderPageBitmap_Start fonksiyonuna yapılan her çağrı (call), PDFium'un (PDFium) sayfayla (page) ilişkilendirdiği (associates) aşamalı durumu (progressive state) tahsis (allocates) eder (allocates) ve (and) bu durum (that state) yalnızca (only) FPDF_RenderPage_Close tarafından (by) serbest bırakılır (is released). Sürücü (drive) döngüsünden (loop) çıkmanın üç yolu vardır. Sayfa tamamlanır (finishes) ve (and) son durum (last status) FPDF_RENDER_DONE olur. Belirteç (token) tetiklenir (trips) ve döngü (loop) erken çıkarak (exits early) iptali (cancellation) bildirir (reporting). Bir şey (Something) başarısız olur (fails) ve (and) durum (status) FPDF_RENDER_FAILED olur. Üçü de (All three) Close'u çağırmalıdır (must call), ve (and) "iptali gör, dışarı çık" ("see cancel, break out") doğal şekli (natural shape), çıkışa (exit) doğru (on its way to) giderken (on its way) temizliği (cleanup) atlama (skip) eğiliminde olduğundan (tends to), iptal yolunun yanlış yapılması (to get wrong) en kolayıdır. Close'a ulaşılmadan bırakılması (Leaving Close unreached) sayfa başına durumun (per-page state) sızmasına (leaks) neden olur (leaks) ve kullanıcının birbirinin ardından gelen oluşturmaları (render after render) iptal etmesine izin veren bir görüntüleyici (viewer) her (every) iptal edilen (aborted) sayfada (page) bu sızıntıyı (leak) biriktirir (would accumulate)
Sağlam (robust) yapı (shape), döngüyü (loop) ve sonuç sınıflandırmasını (result classification) bir try bloğunun içine koyar ve eşleşen (matching) finally bloğunun içine de FPDF_RenderPage_Close'u yerleştirir. Hedef bit eşlem de (destination bitmap) aynı blok içinde (in the same block) yok edilir (is destroyed). İptal etme (Cancellation) döngüyü erken bir Exit ile terk edebilir (can leave) ve finally bloğu yine de çalışır, böylece aşamalı durumu (progressive state) serbest bırakan ve atlanamayacak (cannot be bypassed) tek bir yer tam (exactly) olarak (exactly one) mevcuttur
Status := FPDF_RenderPageBitmap_Start(PdfBmp, FPage, Left, Top,
Width, Height, Ord(Rotation), EncodeRenderOptions(Options), Pause.Pause);
try
while Status = FPDF_RENDER_TOBECONTINUED do
begin
if EffectiveToken.IsCancelled then
begin
Result := prsCancelled;
Exit;
end;
Status := FPDF_RenderPage_Continue(FPage, Pause.Pause);
end;
if EffectiveToken.IsCancelled then
Result := prsCancelled
else if Status = FPDF_RENDER_DONE then
Result := prsDone
else
Result := prsFailed;
finally
// Frees the progressive state Start allocated; mandatory on every path.
FPDF_RenderPage_Close(FPage);
FPDFBitmap_Destroy(PdfBmp);
end;
Döngü, (The loop) içindeki (inside it) geri çağırmaya (callback) dayanmanın yanı sıra (as well as relying on), her Continue'dan önce (before) belirteci (token) kontrol eder (checks). Geri çağırma, mevcut (current) parçayı kısaltır (shortens); döngü kontrolü bir sonrakinin (next one) başlamasını (from starting) durdurur (stops). Birlikte (Together), bir iptalin etki etmesinin (to take effect) ne kadar (how long) sürdüğünü (takes) kabaca (roughly) bir parçanın süresine (duration) sınırlandırırlar (bound)
Üç sonuç (Three outcomes) ve bir iptalden (cancel) sonra (after) bit eşlemin (bitmap) ne tuttuğu (holds)
Herkese açık (public) giriş noktası (entry point) TPdf.RenderPageProgressive'dir (TPdf.RenderPageProgressive) ve prsDone, prsCancelled veya prsFailed'den biri olan bir TPdfProgressiveStatus döndürür. Değerler, PDFium'un FPDF_RENDER_* sabitlerini (constants) Pascal diline özgü bir yapıda (Pascal idiom) yansıtır (mirror) ancak (but) iptal etme (cancellation) durumunu bir hata (error) değil, birinci sınıf (first-class) bir sonuç (result) olarak dahil (fold in) eder
İnsanları (people) yakalayan (catches) nokta (point), hedef bit eşlemin (destination bitmap) prsCancelled'dan sonra (after) ne içerdiğidir. Boş (blank) değildir. PDFium aşamalı (progressively) olarak parçadan sonra (chunk after) aynı bit eşlem parçasına (same bitmap chunk) işler (renders), bu nedenle bir iptal (cancel) döngüyü durdurduğunda (stops the loop), bit eşlem (bitmap) o (that) ana kadar (up to) çizilmiş (painted) olan her şeyi (whatever was) tutar (holds), bu da (which is) kısmi (partial) bir görüntüdür (image): bazı bantlar (bands) bitmiştir (done), geri kalanı (the rest) hala (still) dolgu (fill) rengini göstermektedir (showing). Bu (That) kısmi sonucun yararlı (useful) olup olmadığı (Whether) arayana bağlıdır (depends on). Kullanıcı (user) başka bir yere (elsewhere) gittiği için (navigated) bit eşlemi çöpe atmak üzere olan (about to throw away) bir görüntüleyici (viewer) bunu (it) görmezden (ignore) gelebilir (can simply). Düşük maliyetli (low-cost) bir önizleme (preview) göstermek isteyen bir görüntüleyici (viewer) bunu (it) saklayabilir (can keep). Yapmamanız (must not do) gereken (What you) şey, prsCancelled'ın boş (empty) veya tanımsız (undefined) bir bit eşlem ima (implies) ettiğini varsaymaktır (assume); tamamlanmamış bir oluşturmanın doğru bir anlık görüntüsünü (truthful snapshot) ima eder (implies)
var
Bmp: TBitmap;
Token: IPdfCancellationToken;
Status: TPdfProgressiveStatus;
begin
Bmp := TBitmap.Create;
try
// Token starts un-cancelled; flip Token.IsCancelled from elsewhere
// (a UI action, a navigation event) to abort the render in flight.
Status := Pdf.RenderPageProgressive(Bmp, 0, 0, PageW, PageH, Token);
case Status of
prsDone: Image1.Picture.Assign(Bmp); // fully rendered
prsCancelled: ; // partial bitmap, usually discarded
prsFailed: ShowMessage('Render failed');
end;
finally
Bmp.Free;
end;
end;
Boş belirteç (nil token) ve dal içermeyen (branch-free) geri çağırma (callback) yolu (path)
İptal (Cancellation) bir tercihtir (opt-in). İptal (aborting) etme niyeti (intention) olmadan (with no), yalnızca ileti pompalama faydası (message-pumping benefit) için aşamalı (progressive) oluşturma (rendering) isteyen (just wants) bir çağıranın (caller), belirteç için nil geçebilmesi (be able to pass) gerekir (should). Bunu desteklemenin (to support that) en basit yolu (naive way), geri çağırma (callback) ve döngü boyunca (through) "bir belirteç sağlanmışsa" ("if a token was supplied") kontrollerini dağıtmaktır (scatter), ki bu da (which means) her parça (chunk) üzerinde bir dal (branch) ve hem gerçek (real) bir belirteci (token) hem de onun yokluğunu (absence) işlemek (to handle) zorunda olan bir geri çağırma (callback) anlamına gelir
Uygulama (implementation), çağıran (caller) hiçbir şey iletmediğinde (passes nothing) bir singleton yerine koyarak (by substituting) bundan (that) kaçınır (avoids). Bir nil belirteç (token), IsCancelled değeri her zaman (always) false (yanlış) olan (whose) bir arayüz olan PdfNoCancellationToken ile değiştirilir (is swapped for). O noktadan (From that point) itibaren (onward) geri çağırma (callback) ve döngünün her (every) durumda (case) sorgulayacak (to query) bir belirteci (token) vardır, dolayısıyla (so) ikisinin de (neither) bir nil kontrolüne ihtiyacı yoktur ve ikisinin de özel bir yola ihtiyacı yoktur. Asla iptal etmeyen (never-cancel) belirteç (token) basitçe her zaman false (yanlış) yanıtını verir, geri çağırma her zaman sıfır döndürür ve oluşturma tıpkı (exactly as) iptal edilemeyen (non-cancellable) birinin yapacağı gibi sonuna kadar çalışır (runs to completion). İsteğe bağlı (Optional) davranış (behaviour), bir belirtecin (token) yokluğu (absence) yerine hiçbir zaman (never) tetiklenmeyen (fires) bir belirteç (token) olarak modellenir, ki bu da sıcak yolu (hot path) tekdüze (uniform) tutar (keeps)
// nil -> never-cancel singleton, so the callback path is identical
// whether or not the caller opted into cancellation.
if AToken <> nil then
EffectiveToken := AToken
else
EffectiveToken := PdfNoCancellationToken;
Ortaya çıkan şekil (shape) küçüktür (is small) ve tekrar belirtmeye değerdir (worth restating), çünkü yeniden kullanılabilir parçadır. Geri çağırmayı destekleyen (supports) bir C kütüphanesi (library), durumu (state) bu (that) geri çağırmaya aktarmanız için (to pass) size tam olarak (exactly) tek bir (one) kanal (channel) verir, opak kullanıcı göstericisi (opaque user pointer). Sayılmış (counted) bir Pascal arayüzü referansını (reference) o göstericinin (pointer) arkasına koyun (Put), nesnenin (object) çağrının ortasında (mid-call) toplanmasını (cannot be collected) engellemek için yapının (struct) yanında ikinci (second) bir gerçek (real) referansı (reference) canlı (alive) tutun ve (and) arayüzü (interface) statik bir cdecl fonksiyonu içinde tekrar (back out) okuyun. Tüm tahrik (drive) döngüsünü bir try bloğuna sarın ve yerel bağlamı (native context) finally bloğunda serbest bırakın (free). Pascal kodunun yaşam döngüsü (lifetime) denetimini elinde (in control) tutması (to stay) gereken (has to) ancak (while) C'nin bir gösterici tuttuğu (holds) her aşamalı (progressive) veya geri çağırma tahrikli (callback-driven) PDFium operasyonu (operation) için de aynı şablon (template) geçerlidir
İptal (Cancellation), duyarlı (responsive) bir görüntüleyicinin (viewer) yalnızca bir yarısıdır (one half). Diğer yarısı (The other half), önceden (already) çizdiğiniz (drew) sayfaları (pages) yeniden oluşturmamak (not re-rendering) ve önbelleğe alınmış (cached) bit eşlemleri (bitmaps) sunarak (by serving) yakınlaştırmayı (zoom) ve kaydırmayı (scroll) akıcı tutmaktır, bu (which is) oluşturma önbelleği ve yakınlaştırma performansı hakkındaki makalemizde (our article on) ele alınmıştır (covered in). İptal edilebilir (cancellable) oluşturmanın, navigasyon, seçim ve aramanın yanındaki (alongside) eksiksiz (complete) bir görüntüleyiciye nasıl (how) sığdığı (fits into) hakkında bilgi için, Delphi'deki PDFium VCL bileşeniyle zengin özellikli bir PDF görüntüleyici oluşturma makalesine bakın. Burada (here) açıklanan (described) aşamalı oluşturma (progressive render), bu blogun başka (elsewhere) yerlerinde (on) ele alınan (covered) yükleme, oluşturma ve form API'leriyle birlikte, Delphi ve Lazarus için PDFium Bileşeninin (Component) bir parçası (part) olarak sunulur (ships)