Το PDFium Component ελέγχει τα layers προαιρετικού περιεχομένου PDF (OCGs) στο Delphi μέσα από δύο μεθόδους του TPdf: το InspectOptionalContent απαριθμεί κάθε layer μαζί με την ορατότητα που θα αποδώσει πραγματικά το PDFium, και το SaveAsOptionalContentConfigured γράφει ένα επαληθευμένο αντίγραφο όπου τα layers που διαλέγετε είναι σβηστά ή ανοιχτά. Η δεύτερη μέθοδος εξουδετερώνει επίσης τους κανόνες Usage και /AS που αλλιώς θα ανέτρεπαν σιωπηλά την επεξεργασία σας. Και οι δύο δουλεύουν πάνω στο έγγραφο ήδη ανοιχτό στο TPdf, οπότε δεν υπάρχει δεύτερος parser για να συγχρονίζεται με ό,τι δείχνει ο viewer
Το αίτημα συνήθως έρχεται από γραφείο CAD ή GIS: το σύνολο σχεδίων παραδίδεται με διαστάσεις, σχολιασμούς και πινακίδα τίτλου σε ξεχωριστά layers, και ο πελάτης θέλει ένα αντίγραφο με κρυμμένες τις διαστάσεις πριν πάει σε προμηθευτή. Το PDFium αποδίδει σωστά το προαιρετικό περιεχόμενο, αλλά το public ABI του δεν έχει συνάρτηση να απαριθμήσει OCGs, να διαλέξει configuration ή να αναποδογυρίσει την κατάσταση ενός layer. Οπότε κατεβαίνεις στο επίπεδο αντικειμένων, επεξεργάζεσαι το /OCProperties, αποθηκεύεις, ξαναφορτώνεις, και το layer είναι ακόμη εκεί. Η αιτία είναι η λογική ορατότητας του PDFium, και αξίζει να την καταλάβεις πριν αγγίξεις οποιαδήποτε bytes
Γιατί η επεξεργασία των /ON και /OFF δεν αλλάζει ό,τι αποδίδει το PDFium;
Η επεξεργασία των array /ON και /OFF του λεξικού configuration δεν φτάνει, επειδή το PDFium αφήνει μια ρητή κατάσταση μέσα στο δικό του λεξικό /Usage του OCG να κερδίσει εκείνα τα array, και ένας κανόνας αυτόματης κατάστασης /AS μπορεί μετά να υπερισχύσει και των δύο. Το ISO 32000-1 §8.11.4 περιγράφει configurations και usage dictionaries ως ξεχωριστούς μηχανισμούς· ο renderer του PDFium τα διπλώνει σε μία απόφαση, και το InspectOptionalContent την αναπαράγει με αυτή τη σειρά:
- Ξεκινάς από το
/BaseStateτου configuration, όπου/ONκαι/Unchangedμετρούν και τα δύο ως ορατά και μόνο το/OFFκρύβει - Εφαρμόζεις το array
/ONτου configuration, μετά το/OFFτου, ώστε μια ομάδα που εμφανίζεται και στα δύο να καταλήγει κρυμμένη - Εφαρμόζεις τη ρητή κατάσταση Usage της ομάδας για το ζητούμενο usage, όπως
/Usage << /View << /ViewState /OFF >> >>, που υπερισχύει όλων των παραπάνω - Θεωρείς ορατή μια ομάδα της οποίας το
/Intentδεν περιέχει ούτε/Viewούτε/All, αφού δεν λαμβάνει μέρος στην ορατότητα view-intent - Τέλος τρέχεις το array
/ASτου επιλεγμένου configuration, του οποίου οι εγγραφές για το ταιριαστό event θέτουν την κατάσταση των ομάδων που απαριθμούν
Το τρίτο βήμα είναι αυτό που καίει τον κόσμο. Ένα αρχείο αποθηκευμένο από εργαλείο layout συχνά κουβαλάει /ViewState /ON σε κάθε OCG, και το PDFium τότε αγνοεί το προσεκτικά επεξεργασμένο array /OFF σου: το save πετυχαίνει, το αρχείο ξανανοίγει καθαρά, και το layer συνεχίζει να ζωγραφίζει. Για Print και Export, το OcExplicitUsageState διαβάζει πρώτα PrintState ή ExportState και πέφτει στο ViewState όταν η ειδική εγγραφή απουσιάζει, ώστε ένα μοναχικό ViewState /ON καρφώνει το layer και για εκτύπωση. Το marked content που αναφέρεται σε OCMD (§8.11.2.2) επιλύεται έπειτα απέναντι σε αυτά τα αποτελέσματα ανά ομάδα, μέσω της πολιτικής /P ή, όταν υπάρχει, της έκφρασης ορατότητας /VE
Πώς απαριθμείς τα layers που θα δείξει πραγματικά το PDFium;
Το TPdf.InspectOptionalContent επιστρέφει ένα TPdfOptionalContentInventory του οποίου το array Groups κουβαλάει τον αριθμό αντικειμένου κάθε OCG, το όνομα, τα intents, τις τρεις καταστάσεις Usage, τη γλώσσα, το εύρος zoom, τη σημαία Locked, τον δείκτη radio-group και το υπολογισμένο EffectiveVisible. Η μέθοδος πρώτα βάζει το PDFium να αποθηκεύσει το τρέχον έγγραφο στη μνήμη, αναπτύσσει object streams, και σαρώνει το αποτέλεσμα, ώστε να αντανακλώνται οι επεξεργασίες που έγιναν νωρίτερα στη συνεδρία. Ο δείκτης configuration 0 είναι πάντα το προεπιλεγμένο λεξικό /D και οι εγγραφές του /Configs ακολουθούν από τον δείκτη 1· το προεπιλεγμένο όρισμα -1 διαλέγει τον δείκτη 0. Ένα έγγραφο χωρίς /OCProperties κάνει τη μέθοδο να επιστρέφει False με την αιτία στο ErrorMessage αντί να πετάει exception
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Το Usage έχει προεπιλογή ocuView· το -1 διαλέγει configuration 0, το λεξικό /D
if not Pdf.InspectOptionalContent(Inv) then
begin
Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
Exit;
end;
Memo1.Lines.Add(Format('Configuration %d: %s',
[Inv.SelectedConfigurationIndex,
string(Inv.Configurations[Inv.SelectedConfigurationIndex].Name)]));
for G in Inv.Groups do
Memo1.Lines.Add(Format('obj %d %s visible=%s locked=%s radio=%d',
[G.ObjectNumber, string(G.Name),
BoolToStr(G.EffectiveVisible, True),
BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;
Το array Memberships αναφέρει κάθε OCMD με το Policy του (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), το ακατέργαστο κείμενο VisibilityExpression και το δικό του EffectiveVisible. Μερικοί ακραίοι κανόνες είναι σκόπιμοι. Το /P έχει προεπιλογή το /AnyOn, και ένα OCMD χωρίς ομάδες μετρά ως ορατό. Μια αναφορά σε αριθμό αντικειμένου που δεν είναι γνωστό OCG αντιμετωπίζεται ως ορατή αντί να ρίξει ολόκληρη την έκφραση. Η αξιολόγηση του /VE σταματά σε βάθος εμφώλευσης 32 και θεωρεί οτιδήποτε πιο βαθύ κρυμμένο, που εμποδίζει μια εχθρική ή αυτοαναφερόμενη έκφραση να μετατρέψει την επιθεώρηση σε stack overflow
Γραφή νέας κατάστασης layer με SaveAsOptionalContentConfigured
Το TPdf.SaveAsOptionalContentConfigured παίρνει ένα array από records TPdfOptionalContentStateChange (αριθμός αντικειμένου ομάδας συν Visible) και γράφει ένα έγγραφο όπου το επιλεγμένο configuration παράγει ακριβώς εκείνη την κατάσταση. Το επιλεγμένο configuration παίρνει /BaseState /ON συν ολοκληρωμένα array /ON και /OFF που καλύπτουν κάθε ομάδα, και κάθε OCG που έχει ήδη λεξικό Usage λαμβάνει μια ρητή ViewState (ή PrintState / ExportState, ακολουθώντας το Options.Usage) που ταιριάζει στη νέα του κατάσταση. Με TPdfOptionalContentConfigureOptions.Default, το κλειδί /AS του επιλεγμένου configuration αφαιρείται ώστε ένα event open, print ή export να μην μπορεί να αναποδογυρίσει τα layers πίσω
procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer);
var
Changes: TPdfOptionalContentStateChanges;
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
SetLength(Changes, 2);
Changes[0].GroupObjectNumber := DimensionsObj;
Changes[0].Visible := False;
Changes[1].GroupObjectNumber := NotesObj;
Changes[1].Visible := True;
// Configuration 0, ocuView, DisableAutomaticState και EnforceRadioGroups True
Options := TPdfOptionalContentConfigureOptions.Default;
if not Pdf.SaveAsOptionalContentConfigured('C:\Out\Drawing-NoDims.pdf',
Changes, Options, Report) then
raise Exception.Create('Layer update rejected: ' + Report.ErrorMessage);
Log(Format('%d of %d groups changed, %d Usage states rewritten, /AS removed: %s',
[Report.ChangedGroupCount, Report.GroupCount,
Report.UpdatedUsageStateCount,
BoolToStr(Report.RemovedAutomaticState, True)]));
end;
Η διαδρομή εγγραφής κρατάει την ίδια την αποθηκευμένη έξοδο του PDFium ως πρόθεμα byte προς byte και προσαρτά μόνο τον ξαναγραμμένο ιδιοκτήτη configuration και τα αντικείμενα OCG που κουβαλάνε λεξικά Usage, ακολουθούμενα από μια νέα ενότητα xref και trailer. Πριν έστω ένα byte φτάσει στον προορισμό σας, το αποτέλεσμα ξανανοίγει σε ένα ξεχωριστό TPdf κάτω από την αυστηρή πολιτική φόρτωσης, και η μέθοδος αποτυγχάνει αν ο πίνακας cross-reference δεν επικυρωθεί. Η υπερφόρτωση αρχείου πηγαίνει ένα βήμα παραπέρα: γράφει σε προσωρινό αρχείο δίπλα στον στόχο και αντικαθιστά τον στόχο μόνο αφού η επαλήθευση πετύχει, ώστε μια απορριφθείσα ενημέρωση να μην αφήνει ποτέ πίσω ένα μισογραμμένο σχέδιο. Είναι η ίδια προσέγγιση επαληθευμένης σταδιακής αναθεώρησης που χρησιμοποιεί ο επεξεργαστής name tree και number tree PDF στο PDFium Component
Τι αρνείται να κάνει το configured save;
Το configured save αρνείται κάθε αλλαγή που το ίδιο το έγγραφο απαγορεύει ή δεν μπορεί να αναπαραστήσει με ασφάλεια, και κάθε άρνηση συμβαίνει πριν αγγιχτεί ο προορισμός. Ένας αριθμός αντικειμένου που δεν είναι στο /OCGs αποτυγχάνει ολότελα. Η αλλαγή μιας ομάδας που απαριθμείται στο array /Locked του configuration αποτυγχάνει, αν και η επανάληψη της τρέχουσας τιμής της επιτρέπεται. Με το EnforceRadioGroups ενεργό, κάθε σύνολο /RBGroups που θα κατέληγε με πάνω από ένα ορατό μέλος απορρίπτεται αντί να σβήνει σιωπηλά τα άλλα. Τα κρυπτογραφημένα έγγραφα απορρίπτονται επειδή plaintext σταδιακά αντικείμενα δεν μπορούν να κουβαλήσουν τον ενεργό security handler. Τα υπογεγραμμένα έγγραφα πετάνε EPdfError εκτός αν περάσετε AllowSignedDocument = True, αφού η αλλαγή του τι δείχνει μια σελίδα μπορεί να σπάσει την κάλυψη υπογραφής ή μια πολιτική πιστοποίησης
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // γράφει /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // πρώτη εγγραφή του /Configs, όχι /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // το AllowSignedDocument μένει False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // υπογεγραμμένο αρχείο: τίποτα δεν γράφτηκε στο Target
Result := False;
end;
end;
end;
Γνωρίστε τα αντισταθμίσματα πριν το δέσετε σε batch εργασία. Η προσαρτημένη αναθεώρηση κάθεται πάνω στο πλήρες re-save του PDFium, όχι στα bytes του αρχικού σας αρχείου, που είναι ακριβώς ο λόγος που υπογεγραμμένη είσοδος χρειάζεται ρητή συγκατάθεση. Το ξαναγράψιμο κανονικοποιεί επίσης το επιλεγμένο configuration σε /BaseState /ON, ώστε μια γραμμή βάσης /Unchanged ή /OFF του συγγραφέα αντικαθίσταται από ρητά array με την ίδια προκύπτουσα ορατότητα. Το να πέσει το /AS αφαιρεί κόλπα μόνο για εκτύπωση όπως ένα layer υδατογράφημα που εμφανίζεται μόνο στο χαρτί· ορίστε DisableAutomaticState σε False για να κρατηθούν εκείνοι οι κανόνες, δεχόμενοι ότι μπορεί να υπερισχύσουν της ζητούμενης κατάστασής σας για εκείνο το event. Στη θετική πλευρά, το PDF/A-2 (ISO 19005-2 clause 6.9) και το PDF/UA (ISO 14289-1 clause 7.10) απαγορεύουν και τα δύο το /AS στα λεξικά configuration, οπότε η προεπιλεγμένη έξοδος αφαιρεί ένα ζήτημα που διαφορετικά θα ανέφερε η προετοιμασία και επικύρωση PDF/A με PDFium Component
Πού χωράει ο έλεγχος layers σε έναν viewer PDF Delphi
Σε έναν viewer, ο έλεγχος layers είναι μια λίστα ελέγχου που την τροφοδοτεί το inventory συν ένα reload του αποθηκευμένου αποτελέσματος. Γεμίστε τη λίστα από το Groups, απενεργοποιήστε τις εγγραφές που είναι Locked, θεωρήστε τα μέλη που μοιράζονται RadioGroupIndex αμοιβαία αποκλειόμενα, και στο apply γράψτε σε ένα TMemoryStream και φορτώστε εκείνο το stream πίσω στο TPdf ώστε η προβολή να ζωγραφίσει τη νέα κατάσταση. Οι συνδέσεις ανάμεσα σε TPdf και TPdfView καλύπτονται στο χτίσιμο ενός πλούσιου σε features PDF viewer με PDFium VCL στο Delphi. Αδειοδότηση, trial downloads και το υπόλοιπο feature set βρίσκονται στη σελίδα προϊόντος PDFium Component για Delphi