Το HotPDF γράφει αρχεία PDF linearized, τη διάταξη που το Acrobat ονομάζει Fast Web View, μέσω της ιδιότητας LinearizeOutput στο THotPDF. Ο ορισμός της πριν το BeginDoc κάνει το HotPDF να αναδιατάξει το τελειωμένο γράφημα αντικειμένων ώστε ένας reader ενήμερος για byte-range να μπορεί να εμφανίσει τη σελίδα ένα αφού ανακτήσει μόνο το αρχικό τμήμα του αρχείου, αντί να κατεβάσει πρώτα ολόκληρο το έγγραφο. Ο μηχανισμός είναι το ISO 32000-1 Annex F
Ο λόγος που αυτό έχει σημασία είναι άκομψος. Ένα κανονικό PDF βάζει τον πίνακα cross-reference του στο τέλος, οπότε ένας viewer πρέπει να φτάσει στο τελευταίο byte πριν ξέρει πού βρίσκεται οτιδήποτε. Δώστε σε έναν browser μια σαρωμένη αναφορά 200 σελίδων και ο χρήστης κοιτάζει έναν spinner για ολόκληρη τη μεταφορά, ακόμη κι αν το μόνο που ήθελε ήταν η σελίδα 1. Η linearization το διορθώνει πληρώνοντας ένα κόστος τη στιγμή της εγγραφής. Αυτό το άρθρο αφορά ειδικά αυτή τη διαδρομή εγγραφής, την κατάτμηση, τον βρόχο μέτρησης και τα σκληρά όρια· για το εννοιολογικό υπόβαθρο σχετικά με το τι σας προσφέρει το Fast Web View, η προηγούμενη εξήγηση της linearization PDF και του Fast Web View καλύπτει το έδαφος
Τι εγγυάται στην πραγματικότητα η linearized διάταξη
Ένα linearized αρχείο είναι ένα συνηθισμένο PDF με μια εξαιρετικά συγκεκριμένη φυσική σειρά, και κάθε εγγύηση που προσφέρει προέρχεται από αυτή τη σειρά και όχι από κάποιον νέο τύπο αντικειμένου. Το HotPDF εκπέμπει τα μέρη στη σειρά που ορίζει το Annex F: το λεξικό παραμέτρων linearization μέσα στα πρώτα 1024 bytes, έναν πρώιμο πίνακα cross-reference, τα αντικείμενα επιπέδου εγγράφου, το κύριο hint stream, την πρώτη σελίδα και τα ιδιωτικά της αντικείμενα, μετά τις υπόλοιπες σελίδες, μετά τα κοινά αντικείμενα, μετά όλα τα υπόλοιπα, και τέλος τον κύριο πίνακα cross-reference
Η κατάτμηση παράγεται, δεν δηλώνεται. Το HotPDF διατρέχει το γράφημα αναφορών από κάθε αντικείμενο σελίδας και καταγράφει, για κάθε έμμεσο αντικείμενο, πόσες σελίδες το προσεγγίζουν και ποια σελίδα το προσέγγισε πρώτη. Ένα αντικείμενο που χρησιμοποιείται από ακριβώς μία σελίδα γίνεται ιδιωτικό σε αυτή τη σελίδα. Ένα αντικείμενο που προσεγγίζεται από περισσότερες γίνεται κοινό. Ο κατάλογος, συν ό,τι αναφέρει κάτω από /ViewerPreferences, /OpenAction, /Threads και /AcroForm, συν το λεξικό κρυπτογράφησης όταν η προστασία είναι ενεργή, σχηματίζουν την ομάδα επιπέδου εγγράφου που πρέπει να προηγείται όλων. Οι κόμβοι δέντρου σελίδων κρατούνται σκόπιμα πίσω ώστε να μη μολύνουν το τμήμα πρώτης σελίδας
Το λεξικό παραμέτρων φέρει τους αριθμούς που χρειάζεται ένας reader πριν διαβάσει οτιδήποτε άλλο: /L για το συνολικό μήκος αρχείου, /H για τη θέση και το μήκος του hint stream, /O για τον αριθμό αντικειμένου της πρώτης σελίδας, /E για το byte όπου τελειώνει το τμήμα πρώτης σελίδας, /N για τον αριθμό σελίδων και /T για τη θέση της καταχώρισης του κύριου πίνακα cross-reference. Καθένα από αυτά είναι μια θέση byte σε ένα αρχείο που δεν υπάρχει ακόμη τη στιγμή που χρειάζεται να τα γράψετε
Γιατί πρέπει οι θέσεις του πίνακα hint να συγκλίνουν;
Επειδή οι αριθμοί στο λεξικό παραμέτρων περιγράφουν το αρχείο που τους περιέχει, και η αλλαγή οποιουδήποτε από αυτούς αλλάζει το αρχείο. Αυτή είναι η κεντρική δυσκολία ενός linearized writer, και γι' αυτό το HotPDF μετρά επανειλημμένα αντί να γράφει μία φορά. Διευρύνετε το /T από 6 ψηφία σε 7 και το λεξικό παραμέτρων μεγαλώνει κατά ένα byte· η κεφαλίδα μεγαλώνει· κάθε αντικείμενο μετατοπίζεται· ο κύριος πίνακας cross-reference μετακινείται· το /T τώρα χρειάζεται διαφορετική τιμή. Η διάταξη πρέπει να φτάσει σε σταθερό σημείο πριν δεσμευτεί ούτε ένα byte πραγματικής εξόδου
Το HotPDF το χειρίζεται με φραγμένη επανάληψη. Πρώτα σειριοποιεί κάθε αντικείμενο σε ένα stream μέτρησης που καταγράφει το μήκος χωρίς να κρατά bytes, ώστε κάθε αντικείμενο να έχει γνωστό σειριοποιημένο μέγεθος. Μετά τρέχει ένα πέρασμα διάταξης που αναθέτει θέσεις στην ομάδα επιπέδου εγγράφου, στο hint stream, στην ομάδα πρώτης σελίδας, στις ομάδες μεταγενέστερων σελίδων, στην κοινή ομάδα και στο υπόλοιπο, και αναφέρει πού θα κατέληγε ο κύριος πίνακας cross-reference. Αυτό το αποτέλεσμα τροφοδοτείται πίσω ως είσοδος στο επόμενο πέρασμα. Ο βρόχος έχει ανώτατο όριο οκτώ προσπάθειες, και η μη σύγκλιση προκαλεί εξαίρεση αντί να παράγει ένα αρχείο με ευλογοφανείς λανθασμένες θέσεις
CandidateMainOffset := 0;
for Attempt := 0 to 7 do
begin
CalculateLayout(CandidateMainOffset, FirstXRefData,
HintOffset, EndFirstPage, NewMainOffset);
if NewMainOffset = CandidateMainOffset then
Break;
CandidateMainOffset := NewMainOffset;
end;
if NewMainOffset <> CandidateMainOffset then
raise Exception.Create('Linearization layout did not converge');
Δύο λεπτομέρειες εμποδίζουν τον βρόχο από το να τρεμοπαίζει. Το λεξικό παραμέτρων γράφεται σε μια σταθερή θέση 384 bytes, γεμισμένη με κενά, ώστε η ίδια της η ανάπτυξη να μην μπορεί ποτέ να αποσταθεροποιήσει τη διάταξη· αν το κείμενο του λεξικού ξεπερνούσε ποτέ αυτή τη δέσμευση το HotPDF προκαλεί εξαίρεση αντί να μετατοπίζει σιωπηλά τα πάντα. Και μετά τη σύγκλιση το HotPDF τρέχει ακόμη ένα επιβεβαιωτικό πέρασμα διάταξης και ξανα-ελέγχει το μήκος του hint stream, επειδή το ίδιο το hint stream κωδικοποιεί θέσεις που ήταν γνωστές μόνο αφού η διάταξη σταθεροποιήθηκε. Το κέρδος από όλη αυτή τη μέτρηση είναι ότι το HotPDF ποτέ δεν αποθηκεύει ένα δεύτερο αντίγραφο του εγγράφου: μόλις οι θέσεις καθοριστούν, τα αντικείμενα σειριοποιούνται απευθείας στο stream προορισμού, με μια βεβαίωση σε κάθε όριο τμήματος ότι τα γραμμένα bytes ταιριάζουν με τη θέση που είχε υποσχεθεί
Ενεργοποίησή του από Delphi
Η επιφάνεια API είναι ένα Boolean, και η μόνη απαίτησή του είναι να το ορίσετε πριν αρχίσει η παραγωγή. Το LinearizeOutput έχει προεπιλογή False, και το πέρασμα διάταξης τρέχει όταν γράφεται το έγγραφο, οπότε η ανάθεσή του μετά το EndDoc δεν πετυχαίνει τίποτα
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.FileName := 'fast-view.pdf';
PDF.Version := pdf17;
PDF.LinearizeOutput := True; // must precede BeginDoc
PDF.BeginDoc;
PDF.Canvas.TextOut(72, 72, 'First page');
PDF.EndDoc;
finally
PDF.Free;
end;
end;
Μια προειδοποίηση ανάπτυξης υπερτερεί όλων στην πλευρά του κώδικα. Η linearization αποδίδει μόνο όταν η μεταφορά υποστηρίζει HTTP range requests. Σερβίρετε το ίδιο αρχείο από ένα endpoint που το μεταδίδει ολόκληρο, ή από μια διαμόρφωση CDN που αγνοεί το Range, και έχετε αγοράσει έναν πιο αργό δρόμο εγγραφής και ένα μεγαλύτερο αρχείο χωρίς κανένα ορατό όφελος στον χρήστη. Ελέγξτε τον server πριν ελέγξετε τον κώδικα
Γιατί η linearization παρακάμπτει τα UseXRefStream και UseObjectStreams;
Επειδή ο linearized writer χρειάζεται κάθε αντικείμενο να έχει τη δική του άμεσα διευθυνσιοδοτήσιμη θέση byte, και τα δύο αυτά χαρακτηριστικά το αφαιρούν αυτό. Το HotPDF επομένως εκπέμπει παραδοσιακούς πίνακες cross-reference κειμένου και ασυσκεύαστα έμμεσα αντικείμενα όποτε το LinearizeOutput είναι ενεργό, ακόμη κι αν ο καλών όρισε επίσης UseXRefStream ή UseObjectStreams. Αυτή είναι σκόπιμη παράκαμψη, όχι σύγκρουση που πρέπει να επιλύσετε μόνοι σας
Η λογική ακολουθεί από τους πίνακες hint. Ένας πίνακας hint περιγράφει πού ξεκινά ένα τμήμα σελίδας και πόσο μεγάλο είναι, ώστε ένας reader να μπορεί να ζητήσει ακριβώς αυτό το εύρος. Ένα αντικείμενο συσκευασμένο σε ένα container /ObjStm δεν έχει καθόλου ανεξάρτητη θέση· υπάρχει μόνο ως τμήμα μέσα σε άλλο συμπιεσμένο stream που πρέπει να ανακτηθεί και να αποσυμπιεστεί ως μονάδα. Αν βασιζόσασταν στα object streams για μέγεθος αρχείου, καταλάβετε ότι η linearization και η συμπίεση τραβούν προς αντίθετες κατευθύνσεις εδώ, και διαβάστε τον συμβιβασμό στο συνοδευτικό κείμενο για τα object streams και τις αυξητικές ενημερώσεις στο HotPDF. Η ίδια ένταση διαμορφώνει τα αρχεία υβριδικής αναφοράς, που υπάρχουν ακριβώς για να κρατούν παλαιότερους readers να δουλεύουν παράλληλα με πίνακες βασισμένους σε streams, όπως καλύπτεται στο άρθρο για τα υβριδικά cross-reference streams σε PDF παραγόμενα από Office
Υπάρχει επίσης ένα κατώτατο όριο έκδοσης. Η linearization απαιτεί PDF 1.2 ή νεότερο. Αν η επιλεγμένη έκδοση είναι παλαιότερη, το HotPDF την ανεβάζει αυτόματα, εκτός αν έχει οριστεί StrictVersionLock, οπότε η εγγραφή προκαλεί εξαίρεση αντί να προωθεί ήσυχα ένα έγγραφο που καθηλώσατε επίτηδες
Το τείχος των 4 GiB, και γιατί το HotPDF αρνείται αντί να περικόπτει
Οι πίνακες hint linearization αποθηκεύουν θέσεις ως τιμές 32-bit, οπότε ένα linearized αρχείο δεν μπορεί να διευθυνσιοδοτήσει τίποτα στα ή πέρα από 4 GiB, και το HotPDF απορρίπτει τέτοια έξοδο με ρητή εξαίρεση αντί να γράφει ένα αρχείο με τυλιγμένες θέσεις. Το όριο δεν είναι επιλογή υλοποίησης του HotPDF· είναι το πλάτος των πεδίων που ορίζει το Annex F
Ο έλεγχος εφαρμόζεται σε τρία σημεία, και και τα τρία έχουν σημασία. Το HotPDF επικυρώνει κάθε αντικείμενο μόλις γίνει γνωστό το σειριοποιημένο μήκος του, επικυρώνει το μήκος κάθε τμήματος σελίδας κατά τη δημιουργία των καταχωρίσεων hint, και επικυρώνει το τελικό μήκος αρχείου αφού μεγεθυνθεί ο κύριος πίνακας cross-reference. Η πρώιμη αποτυχία είναι το όλο νόημα: ένας πίνακας hint με μια σιωπηλά περικομμένη θέση παράγει ένα αρχείο που ανοίγει σωστά σε έναν viewer που το κατεβάζει ολόκληρο και αποτυγχάνει μόνο για τον client byte-range για τον οποίο υπήρχε η linearization, που είναι η χειρότερη δυνατή κατάσταση αποτυχίας επειδή ο viewer δοκιμής σας ποτέ δεν την αναπαράγει. Αν παράγετε έξοδο πολλών gigabyte, η linearization δεν είναι το εργαλείο, και η προσέγγιση streaming που περιγράφεται στις σημειώσεις για το Direct File API για ροές εργασίας μεγάλων PDF είναι η κατεύθυνση προς την οποία πρέπει να κοιτάξετε
Ανίχνευση linearization σε ένα αρχείο που φορτώσατε
Η THotPDF.IsLoadedLinearized αναφέρει αν το έγγραφο που είναι τρέχον φορτωμένο είχε ήδη γραφτεί σε linearized μορφή, και απαντά από μια στιγμιαία εικόνα ληφθείσα πριν την ανάλυση, όχι από το ζωντανό stream. Το HotPDF διαβάζει τα πρώτα 1024 bytes από τη θέση μηδέν του stream πηγής, τα σαρώνει για την πρώτη λέξη-κλειδί obj και μετά για μια καταχώριση /Linearized με τιμή 1, και αποθηκεύει στην cache το boolean αποτέλεσμα
var
PDF: THotPDF;
PageCount: Integer;
begin
PDF := THotPDF.Create(nil);
try
PageCount := PDF.LoadFromFile('incoming.pdf');
if (PageCount > 0) and (not PDF.IsLoadedLinearized) then
Writeln('Source is not Fast Web View ready');
finally
PDF.Free;
end;
end;
Δύο περιορισμοί σε αυτή την περιγραφή είναι θεμελιώδεις. Η ανίχνευση δεν μπορεί να βασιστεί στη θέση του stream, επειδή μέχρι τη στιγμή που ο κώδικας εφαρμογής κάνει την ερώτηση ο parser την έχει μετακινήσει, και δεν μπορεί να ξαναδιαβάσει κατά παραγγελία επειδή η LoadFromFile απελευθερώνει το εσωτερικό stream πηγής μόλις τελειώσει η φόρτωση. Εξ ου και ο σχεδιασμός σύλληψης-πριν-την-ανάλυση-και-cache. Η σάρωση είναι επίσης σκόπιμα κυριολεκτική για την τιμή: μόνο /Linearized 1 ή μια αριθμητικά ισοδύναμη μορφή με μηδενικό κλάσμα γίνεται αποδεκτή, επειδή ένα αρχείο του οποίου το λεξικό παραμέτρων λέει κάτι άλλο δεν κάνει την υπόσχεση του Annex F
Μια παγίδα εγγραφής Delphi που αξίζει να κλέψετε
Τοπικές εγγραφές που περιέχουν δυναμικούς πίνακες αρχικοποιούν τα διαχειριζόμενα πεδία τους και τίποτα άλλο, και αν κρατάτε ένα απλό πεδίο Count δίπλα στον πίνακα πρέπει να το καθαρίσετε μόνοι σας. Αυτό δάγκωσε την κατάτμηση linearization κατά την ανάπτυξη, και είναι το είδος σφάλματος που κοστίζει μια ημέρα ακριβώς επειδή μία πλατφόρμα το κρύβει
type
THPDFLinearIndexList = record
Values: THPDFIntegerArray; // managed field: cleared for you
Count: Integer; // plain field: whatever was on the stack
end;
// Required, not cosmetic:
Part4 := Default(THPDFLinearIndexList);
Part6 := Default(THPDFLinearIndexList);
Part8 := Default(THPDFLinearIndexList);
Part9 := Default(THPDFLinearIndexList);
Το πεδίο δυναμικού πίνακα μετρά αναφορές, οπότε ο μεταγλωττιστής το μηδενίζει. Το Count δίπλα του είναι ένας συνηθισμένος ακέραιος χωρίς τέτοια εγγύηση, και ένα μη αρχικοποιημένο Count στέλνει την πρώτη κιόλας προσάρτηση σε τυχαία θέση. Στο Win32 η θέση στη στοίβα έτυχε να κρατά μηδέν, η προσάρτηση κατέληξε στη θέση 0, και κάθε δοκιμή περνούσε. Στο Win64 ο ίδιος κώδικας έγραφε πέρα από το τέλος του πίνακα. Το μάθημα γενικεύεται πολύ πέρα από τη linearization: όταν μια εγγραφή αναμειγνύει διαχειριζόμενα και μη διαχειριζόμενα πεδία, αναθέστε Default(TRecord) και σταματήστε να σκέφτεστε ποια πεδία καλύπτει ο μεταγλωττιστής, και ποτέ μην αντιμετωπίζετε μια πράσινη εκτέλεση Win32 ως απόδειξη ότι η αρχικοποίηση είναι σωστή
Τα μέλη LinearizeOutput και IsLoadedLinearized που περιγράφονται εδώ διατίθενται με το τυπικό HotPDF Component για Delphi και C++Builder· η σελίδα προϊόντος φέρει την πλήρη αναφορά ιδιοτήτων, συμπεριλαμβανομένων των κανόνων αλληλεπίδρασης με τα cross-reference streams, τα object streams και το κλείδωμα έκδοσης