Η κλάση THPDFBackgroundRenderer του HotPDF είναι ένας απόγονος TThread που αποδίδει φορτωμένες σελίδες PDF σε bitmap σε ένα νήμα εργασίας, ώστε ένας viewer σε Delphi να μπορεί να συνεχίζει την κύλιση και την επανασχεδίαση ενώ μια σελίδα ραστεροποιείται ακόμη στο παρασκήνιο. Το THPDFBackgroundRenderer.RequestPage βάζει σε ουρά έναν δείκτη σελίδας για εκείνο το νήμα εργασίας, το CancelAll αφαιρεί ό,τι εξακολουθεί να περιμένει, και το GetCachedBitmap παραδίδει πίσω ένα ολοκληρωμένο bitmap που ο καλών κατέχει και πρέπει να ελευθερώσει. Κυλήστε ένα σαρωμένο συμβόλαιο διακοσίων σελίδων σε ανάλυση εκτύπωσης μόνο στο νήμα UI και κάθε αλλαγή σελίδας παγώνει το παράθυρο μέχρι το GDI να ολοκληρώσει τη σχεδίασή της, ακριβώς το τράνταγμα που το THPDFBackgroundRenderer υπάρχει για να εξαλείψει
Γιατί να αποδίδετε καθόλου σελίδες PDF σε νήμα παρασκηνίου;
Ένα νήμα παρασκηνίου κερδίζει την πολυπλοκότητά του επειδή ο αποδότης σελίδων του HotPDF είναι γνήσιος διερμηνέας ροής περιεχομένου, όχι μια φθηνή αντιγραφή bitmap που επιστρέφει πριν το προσέξει κανείς: διατρέχει τελεστές PDF, κρατά μια στοίβα κατάστασης γραφικών, και ραστεροποιεί διαδρομές, εικόνες και γλύφους μέσω GDI, την ίδια μηχανή που καλύπτεται στην απόδοση φορτωμένων σελίδων PDF σε TBitmap. Εκτελέστε αυτή τη δουλειά σύγχρονα μέσα σε έναν χειριστή κύλισης ή ζωγραφικής και ο βρόχος μηνυμάτων σταματά την άντληση μέχρι να επιστρέψει η κλήση, κάτι που είναι ακριβώς αυτό που σημαίνει ένα παγωμένο παράθυρο. Η προσθήκη Application.ProcessMessages μέσα στην κλήση απόδοσης δεν το διορθώνει: αφήνει την ουρά μηνυμάτων να αδειάσει, αλλά η ίδια η απόδοση εξακολουθεί να κατέχει το καλούν νήμα, οπότε το παράθυρο επανασχεδιάζει παρωχημένο περιεχόμενο πιο γρήγορα ενώ η πραγματική δουλειά δεν έχει προχωρήσει πουθενά. Ο μόνος τρόπος να διατηρήσετε έναν viewer αποκρίσιμο κατά τη διάρκεια μιας γνήσια αργής απόδοσης είναι να εκτελέσετε αυτή την απόδοση κάπου αλλού, γι' αυτό το THPDFBackgroundRenderer υπάρχει ως υποκλάση TThread αντί για callback ή timer
Ρύθμιση μιας ουράς αιτημάτων για έναν κυλιόμενο viewer
Το THPDFBackgroundRenderer.Create δέχεται το φορτωμένο στιγμιότυπο THotPDF και ένα DPI που παραμένει σταθερό για ολόκληρη τη ζωή αυτού του αποδότη, οπότε κάθε σελίδα που μπαίνει σε ουρά μέσω ενός στιγμιότυπου αποδίδεται σε μία ανάλυση· ένας viewer που υποστηρίζει zoom χρειάζεται έναν νέο αποδότη, όχι μια νέα ιδιότητα DPI, κάθε φορά που αλλάζει το επίπεδο zoom. Το RequestPage προσαρτά έναν δείκτη σελίδας σε μια εσωτερική ουρά και επιστρέφει αμέσως: δεν κάνει καθόλου απόδοση το ίδιο και ποτέ δεν αγγίζει το νήμα UI. Το Execute, το κληρονομημένο σημείο εισόδου TThread που το HotPDF εκτελεί μόλις καλέσετε το Start, τραβά έναν δείκτη τη φορά από την αρχή αυτής της ουράς, τον αποδίδει μέσω της προσωρινής μνήμης σελίδων του εγγράφου, και αποθηκεύει ένα αντίγραφο με κλειδί τη σελίδα ώστε το GetCachedBitmap να μπορεί να το παραδώσει πίσω αργότερα
type
TViewerForm = class(TForm)
RenderPollTimer: TTimer;
procedure RenderPollTimerTimer(Sender: TObject);
private
FDoc: THotPDF;
FRenderer: THPDFBackgroundRenderer;
FPendingPage: Integer;
procedure RequestPageWindow(CenterPage: Integer);
end;
procedure TViewerForm.RequestPageWindow(CenterPage: Integer);
var
I: Integer;
begin
if FRenderer <> nil then
begin
FRenderer.CancelAll;
FRenderer.Free;
end;
FRenderer := THPDFBackgroundRenderer.Create(FDoc, 150);
for I := CenterPage - 1 to CenterPage + 1 do
if (I >= 0) and (I < FDoc.LoadedPageCount) then
FRenderer.RequestPage(I);
FPendingPage := CenterPage;
FRenderer.Start;
end;
procedure TViewerForm.RenderPollTimerTimer(Sender: TObject);
var
Bmp: TBitmap;
begin
if FRenderer = nil then Exit;
Bmp := FRenderer.GetCachedBitmap(FPendingPage);
if Bmp <> nil then
begin
PageImage.Picture.Bitmap.Assign(Bmp);
Bmp.Free;
end;
end;
Το GetCachedBitmap επιστρέφει nil μέχρι να είναι έτοιμο το αντίγραφο εκείνης της σελίδας, οπότε ένα μοτίβο δειγματοληψίας μέσω timer όπως το παραπάνω αρκεί· δεν υπάρχει ξεχωριστό συμβάν ετοιμότητας για καλωδίωση, το HotPDF το επιλύει αυτό με έναν απλό έλεγχο nil αντί για ένα μεγαλύτερο API ειδοποίησης. Η επόμενη ενότητα καλύπτει τι κάνει πράγματι το CancelAll και εκείνη η κλήση Free, επειδή και τα δύο έχουν σημασία μόλις σελίδες αρχίσουν να αποδίδονται εκτός σειράς ή μια κύλιση συμβεί πιο γρήγορα από όσο μπορεί να αδειάσει η ουρά
Η συντόμευση μίας κλήσης για μία μόνο σελίδα
Το THotPDF.RenderLoadedPageToBitmapAsync υπάρχει για τη συνηθισμένη περίπτωση της εκκίνησης ακριβώς μίας σελίδας χωρίς να αγγίζετε απευθείας το THPDFBackgroundRenderer: κατασκευάζει τον αποδότη εσωτερικά, καλεί το RequestPage μία φορά, ξεκινά το νήμα, και επιστρέφει την αναφορά TThread στον καλούντα, ο οποίος την κατέχει και είναι υπεύθυνος για την ελευθέρωσή της. Η ανάκτηση του αποτελέσματος περνά μέσα από το THotPDF.GetLoadedCachedRenderedBitmap αντί από το δικό του GetCachedBitmap του αποδότη, επειδή το GetLoadedCachedRenderedBitmap διαβάζει την κοινόχρηστη προσωρινή μνήμη του εγγράφου με κλειδί τον δείκτη σελίδας και το DPI, την ίδια προσωρινή μνήμη που το RenderLoadedPageToBitmapCached και ο ενσωματωμένος prefetcher ήδη γεμίζουν — μια σελίδα που κάποιο άλλο μέρος του viewer έχει ήδη αποδώσει σε εκείνο το DPI μπορεί να επιστρέψει αμέσως, προτού το νήμα παρασκηνίου που μόλις ξεκίνησε έχει καν προγραμματιστεί από το λειτουργικό σύστημα
// A simpler alternative to the queue above, for one page at a time.
procedure TViewerForm.RequestSinglePage(PageIndex: Integer);
begin
if FAsyncWorker <> nil then
FAsyncWorker.Free; // waits if a prior page is still rendering
FAsyncWorker := Pdf.RenderLoadedPageToBitmapAsync(PageIndex, 150);
FPendingPage := PageIndex;
end;
procedure TViewerForm.AsyncPollTimerTimer(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := Pdf.GetLoadedCachedRenderedBitmap(FPendingPage, 150);
if Bmp <> nil then
begin
PageImage.Picture.Bitmap.Assign(Bmp);
Bmp.Free;
end;
end;
Μπορείτε να ακυρώσετε μια σελίδα που βρίσκεται ήδη σε ουρά;
Το CancelAll αφαιρεί μόνο εργασίες που εξακολουθούν να κάθονται στην ουρά· μια σελίδα που το HotPDF έχει ήδη τραβήξει από την αρχή και έχει παραδώσει στην κλήση απόδοσής της συνεχίζει μέχρι την ολοκλήρωση, επειδή το THPDFBackgroundRenderer δεν έχει κανέναν μηχανισμό να διακόψει δουλειά που ήδη βρίσκεται σε εξέλιξη. Αυτό είναι ένα λογικό συμβιβασμό στην πράξη — η απόδοση μιας μεμονωμένης σελίδας σπάνια διαρκεί αρκετά ώστε να αξίζει την πρόσθετη πολυπλοκότητα της προεκτόπισης — αλλά μια γρήγορη κύλιση που πυροδοτεί CancelAll σε κάθε συμβάν κύλισης εξακολουθεί να πληρώνει για όποια μία σελίδα βρισκόταν στη μέση της απόδοσής της τη στιγμή κάθε ακύρωσης. Η επίσημη τεκμηρίωση είναι σαφής σχετικά με αυτό: μια απόδοση που ήδη εκτελείται μπορεί να ολοκληρωθεί πριν τερματιστεί το νήμα
Το Execute έχει μια δεύτερη, εύκολα παραβλέψιμη συμπεριφορά: ο βρόχος εξέρχεται μόλις βρει την ουρά άδεια, δεν παραμένει αδρανής περιμένοντας να φτάσει περισσότερη δουλειά. Ένα στιγμιότυπο THPDFBackgroundRenderer είναι επομένως ένας εργάτης παρτίδας μίας χρήσης, όχι μια επίμονη υπηρεσία παρασκηνίου — βάλτε σε ουρά μερικές σελίδες, καλέστε το Start, και μόλις αποδοθεί η τελευταία σελίδα σε ουρά το υποκείμενο νήμα του λειτουργικού συστήματος τερματίζεται μόνο του. Η επανάκληση του RequestPage στο ίδιο στιγμιότυπο αφού το Execute έχει ήδη αδειάσει την ουρά δεν το επανεκκινεί, κάτι που εξηγεί ακριβώς γιατί το RequestPageWindow παραπάνω αντικαθιστά το στιγμιότυπο αποδότη σε κάθε κλήση αντί να προσπαθεί να συνεχίσει να τροφοδοτεί ένα μακρόβιο αντικείμενο
Είναι ασφαλές να αγγίζετε ένα TBitmap από νήμα παρασκηνίου σε Delphi;
Το άγγιγμα ενός TBitmap από νήμα παρασκηνίου είναι ασφαλές στον σχεδιασμό του HotPDF εφόσον μόνο ένα νήμα ποτέ λειτουργεί σε ένα δεδομένο στιγμιότυπο bitmap κάθε φορά, και το THPDFBackgroundRenderer επιβάλλει αυτό το όριο αντί να το αφήνει στον καλούντα. Το Execute αποδίδει κάθε σελίδα μέσα στο δικό του κλείδωμα απόδοσης του εγγράφου, το ίδιο κρίσιμο τμήμα που κάθε κλήση RenderLoadedPageToBitmapCached και ο ενσωματωμένος prefetcher PrefetchLoadedPages ήδη μοιράζονται, οπότε η πραγματική σχεδίαση GDI για μια δεδομένη σελίδα συμβαίνει σε ακριβώς ένα νήμα τη φορά και ποτέ δεν επικαλύπτεται με άλλη απόδοση εκείνου του εγγράφου. Το προκύπτον bitmap είναι ένα αντικείμενο που ανήκει στο νήμα εργασίας και το THPDFBackgroundRenderer ποτέ δεν το δημοσιεύει απευθείας σε έναν καλούντα
Το GetCachedBitmap αντ' αυτού εκχωρεί ένα εντελώς νέο TBitmap και καλεί το Assign πάνω του κάτω από το δικό του ξεχωριστό κλείδωμα του αποδότη, οπότε το αντίγραφο συμβαίνει πάντα ενώ το Execute εμποδίζεται να αντικαταστήσει εκείνη τη θέση προσωρινής μνήμης από κάτω του — το καλούν νήμα παίρνει δεδομένα pixel, ποτέ τον αρχικό χειριστή. Αυτός ο διαχωρισμός είναι επίσης ο λόγος να αντισταθείτε στην ιδέα ενός προσαρμοσμένου νήματος απόδοσης που καλεί απευθείας τις συναρτήσεις απόδοσης του HotPDF χωρίς να περνά από το THPDFBackgroundRenderer ή το PrefetchLoadedPages: δύο αποδόσεις που ανταγωνίζονται πάνω στις ίδιες κοινόχρηστες προσωρινές μνήμες και το γράφημα αντικειμένων του ίδιου φορτωμένου εγγράφου είναι ακριβώς το σενάριο που το εσωτερικό κλείδωμα του HotPDF υπάρχει για να αποτρέψει, και η κλάση αποδότη παρασκηνίου σας δίνει αυτό το κλείδωμα δωρεάν αντί να το επαναϋλοποιείτε
Πώς διαφέρει αυτό από την ενσωματωμένη προανάκτηση σελίδων του HotPDF;
Το PrefetchLoadedPages και το THPDFBackgroundRenderer λύνουν σχετικά αλλά διαφορετικά προβλήματα: το PrefetchLoadedPages, δεδομένου ενός εύρους σελίδων, αποδίδει ολόκληρη εκείνη τη γειτονιά στην κοινόχρηστη προσωρινή μνήμη του εγγράφου αυτόματα στο δικό του νήμα εργασίας, χωρίς αντικείμενο ουράς για τον καλούντα να δημιουργήσει ή να διαχειριστεί. Το THPDFBackgroundRenderer ανταλλάσσει αυτή την αυτοματοποίηση για έλεγχο — ο καλών αποφασίζει ακριβώς ποιοι δείκτες σελίδων έχουν σημασία και με ποια σειρά, και μπορεί να ακυρώσει όσους παραμένουν σε ουρά χωρίς να αγγίξει όποιο εύρος θερμαίνει αλλού ο ενσωματωμένος prefetcher. Και τα δύο περνούν μέσα από το ίδιο κλείδωμα απόδοσης, οπότε ένας viewer μπορεί να εκτελεί το PrefetchLoadedPages για τη συνηθισμένη περίπτωση των επόμενων λίγων σελίδων και να καταφεύγει στο THPDFBackgroundRenderer μόνο όταν προκύψει κάτι έξω από εκείνο το μοτίβο, όπως μια λωρίδα μικρογραφιών που πηδά απευθείας σε μια σελίδα που μόλις έκανε κλικ ο χρήστης
begin
// PrefetchLoadedPages takes a 1-based "start-end" range string, while
// RequestPage below stays 0-based like every other loaded-page index.
Pdf.PrefetchLoadedPages(Format('%d-%d', [CenterPage + 1, CenterPage + 5]), 150);
// Reach for THPDFBackgroundRenderer only for a page outside that
// window, such as a thumbnail the user just clicked.
FRenderer := THPDFBackgroundRenderer.Create(Pdf, 150);
FRenderer.RequestPage(ClickedThumbnailPage);
FRenderer.Start;
end;
Δύο λεπτομέρειες κύκλου ζωής αξίζει να τις μεταφέρετε σε παραγωγικό κώδικα. Η προσωρινή μνήμη σε επίπεδο εγγράφου πίσω από το RenderLoadedPageToBitmapCached περιορίζεται από το RenderCacheCapacity, οκτώ σελίδες εξ ορισμού, και εξοβελίζει την λιγότερο πρόσφατα χρησιμοποιημένη καταχώριση μόλις γεμίσει, αλλά η δική του λίστα αποτελεσμάτων ενός στιγμιότυπου THPDFBackgroundRenderer δεν έχει τέτοιο όριο — κρατά ένα bitmap ανά ξεχωριστό δείκτη σελίδας που ζητήθηκε ποτέ μέσω εκείνου του στιγμιότυπου μέχρι να ελευθερωθεί το ίδιο το στιγμιότυπο, οπότε ένας αποδότης που διατηρείται ζωντανός για ολόκληρη μια συνεδρία κύλισης σε υψηλό DPI θα συσσωρεύσει ευχαρίστως ένα bitmap πλήρους ανάλυσης ανά σελίδα που κύλισε. Το HotPDF επίσης δεν ακυρώνει αυτόματα έναν αποδότη δημιουργημένο από τον καλούντα με τον τρόπο που ακυρώνει τον δικό του prefetcher πριν φορτωθεί ή καταστραφεί ένα έγγραφο, αφού ένα στιγμιότυπο THPDFBackgroundRenderer ποτέ δεν καταχωρείται στο αντικείμενο THotPDF στο οποίο δείχνει — έτσι ο καλών κώδικας πρέπει να ακυρώνει και να ελευθερώνει κάθε αποδότη χτισμένο έναντι ενός εγγράφου πριν επαναφορτώσει ή ελευθερώσει εκείνο το έγγραφο, την ίδια πειθαρχία σειράς που εφαρμόζει το HotPDF εσωτερικά στο PrefetchLoadedPages
Το THPDFBackgroundRenderer είναι ένα κομμάτι της πρόσοψης φορτωμένου εγγράφου πίσω από την αρχιτεκτονική viewer MVC του HotPDF, και συνδυάζεται φυσικά με τις ροές εργασίας σε επίπεδο αρχείου στο Direct File API για μεγάλα PDF όταν το έγγραφο που κυλά είναι το ίδιο πολύ μεγάλο για να φορτωθεί πρόχειρα εξαρχής. Η απόδοση στο παρασκήνιο, οι ουρές αιτημάτων, και η προσωρινή μνήμη απόδοσης που περιγράφονται εδώ αποτελούν όλα μέρος του τυπικού εξαρτήματος HotPDF για Delphi και C++Builder