Τεχνικό Άρθρο

Εισαγωγή διανυσματικών EMF και WMF σε Delphi PDF με το HotPDF

Το HotPDF, το εγγενές εξάρτημα PDF για Delphi και C++Builder, εισάγει τα μεταπεριγράμματα EMF και WMF των Windows διερμηνεύοντας κάθε εγγραφή GDI απευθείας σε τελεστές PDF αντί να ισοπεδώνει το αρχείο σε bitmap: οι διαβαθμισμένες γεμίσεις γίνονται πρότυπα αξονικής σκίασης PDF, τα βουρτσάκια hatch γίνονται πρότυπα παράθεσης PDF, και μια κεντρική πύλη κατάστασης διαδρομής εμποδίζει τις κακοσχηματισμένες εγγραφές να αλλοιώσουν την έξοδο. Κάθε γράφημα που ένα TChart, μια επιφάνεια GDI+, ή ένα απλό TCanvas μπορεί να εξάγει ως enhanced metafile είναι υποψήφιο για αυτή τη διαδρομή, και η διαφορά φαίνεται τη στιγμή που κάποιος κάνει zoom στη σελίδα ή τη στέλνει σε εκτυπωτή υψηλής ανάλυσης

Η εναλλακτική στην οποία καταφεύγουν οι περισσότεροι προγραμματιστές Delphi εξ ορισμού είναι η ραστεροποίηση του metafile σε bitmap πριν το τοποθετήσουν στη σελίδα, και το κόστος φαίνεται μόνο αργότερα: ένα ραβδόγραμμα που ήταν ευκρινές στην οθόνη γίνεται εμφανώς τετραγωνισμένο τη στιγμή που το PDF εκτυπώνεται στα 600 DPI ή προβάλλεται σε οθόνη αίθουσας συσκέψεων, και μια περιοχή CAD γεμάτη με hatch καταρρέει σε ένα ενιαίο επίπεδο γκρι ορθογώνιο αν το στυλ γεμίσματος δεν μεταφερθεί. Η ανάγνωση του metafile ως πρόγραμμα αντί ως εικόνα είναι αυτό που αποφεύγει και τα δύο προβλήματα, και είναι ο πιο δύσκολος δρόμος για σωστή υλοποίηση, γι' αυτό αξίζει να γνωρίζετε τις παγίδες παρακάτω πριν κυκλοφορήσει μια αναφορά

Γιατί να διερμηνεύσετε ένα metafile αντί να το ισοπεδώσετε σε bitmap;

Το HotPDF διατηρεί την εισαγωγή EMF και WMF στη διανυσματική διαδρομή επειδή ένα metafile των Windows είναι μια καταγεγραμμένη ακολουθία κλήσεων σχεδίασης GDI, όχι μια εικόνα, και η επανάληψη αυτών των κλήσεων ως τελεστές διαδρομής, κειμένου και σκίασης PDF είναι αυτό που επιτρέπει στο αποτέλεσμα να κλιμακώνεται όπως η υπόλοιπη σελίδα. Τα THPDFPage.ShowMetafile και το αντίστοιχό του ShowMetafileEx είναι τα σημεία εισόδου που καλεί μια εφαρμογή, και και τα δύο παραδίδουν το metafile στο THPDFWmf, την κλάση που διατρέχει κάθε εγγραφή GDI και τη μεταφράζει. Η διάκριση δεν είναι απόλυτη, και το HotPDF δεν προσποιείται το αντίθετο: μια εγγραφή metafile που είναι γνησίως δεδομένα raster, ένα blit bitmap StretchDIBits για παράδειγμα, ενσωματώνεται ως πραγματικό PDF Image XObject μέσω των AddImage και ShowImage, το ίδιο ζεύγος κλήσεων από το οποίο περνά οποιαδήποτε άλλη εικόνα στη σελίδα, αντί να εξαναγκαστεί σε τελεστές διαδρομής που δεν μπορούν να εκφράσουν μια φωτογραφία. Οι γραμμές, οι γεμίσεις, και το κείμενο παραμένουν διανυσματικά· τα pixel που ήταν ήδη pixel στην πηγή παραμένουν pixel στην έξοδο. Η απλούστερη κλήση δεν χρειάζεται τίποτα πέρα από το φορτωμένο metafile:

var
  Pdf: THotPDF;
  Chart: TMetafile;
begin
  Pdf := THotPDF.Create(nil);
  Chart := TMetafile.Create;
  try
    Chart.LoadFromFile('quarterly-revenue.emf');  // exported from TChart or GDI+
    Pdf.FileName := 'quarterly-report.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafile(Chart);
    Pdf.EndDoc;
  finally
    Chart.Free;
    Pdf.Free;
  end;
end;

Πώς μετατρέπει ο διερμηνέας τις συντεταγμένες GDI σε χώρο σελίδας PDF;

Το HotPDF απαντά σε αυτό με ένα μοναδικό πέρασμα πάνω από τη δική του ροή εγγραφών του metafile αντί για μια δεύτερη υλοποίηση του GDI. Το THPDFWmf.Analyse διαβάζει την κεφαλίδα του metafile μέσω της κλήσης Win32 GetEnhMetaFileHeader, επαναφέρει την εσωτερική του κατάσταση σχεδίασης, και καλεί το EnumEnhMetafile, το ίδιο API απαρίθμησης που θα χρησιμοποιούσε ένας viewer metafile, ώστε κάθε εγγραφή EMR_* να φτάνει στο THPDFWmf.ExecuteRecord με τη σειρά που καταγράφηκε αρχικά. Το GDI εκφράζει συντεταγμένες από πάνω προς τα κάτω σε μονάδες συσκευής ή λογικές μονάδες που επιλέγει η δική του λειτουργία χαρτογράφησης του metafile· μια σελίδα PDF είναι από κάτω προς τα πάνω σε σημεία χώρου χρήστη, το σύστημα συντεταγμένων που καλύπτεται στο μοντέλο σχεδίασης καμβά του HotPDF για διαδρομές και γεμίσεις. Κάθε χειριστής εγγραφής επιλύει αυτή την ασυμφωνία μέσω των ScaleX και ScaleY, τα οποία καλούν τα ProjectX και ProjectY για να επαναλάβουν τον δικό του τύπο παραθύρου-προς-viewport του GDI για τις ανισότροπες και ισότροπες λειτουργίες χαρτογράφησης, ώστε ένα σχήμα που καταγράφηκε με πλάτος πέντε λογικών μονάδων να καταλήγει στο σωστό πλάτος σε σημεία PDF ανεξάρτητα από τις εκτάσεις παραθύρου και viewport που όρισε η εφαρμογή προέλευσης

Πώς γίνεται μια διαβαθμισμένη γέμιση GDI πρότυπο σκίασης PDF;

Μια εγγραφή EMR_GRADIENTFILL γίνεται πραγματικό πρότυπο αξονικής σκίασης PDF Type 2 (ISO 32000-1 §8.7.4.5) όποτε το GDI την κατέγραψε σε μία από τις δύο λειτουργίες ορθογωνίου. Το THPDFWmf.VEMRGradientFill διαβάζει τη δική του διάταξη της εγγραφής απευθείας από την ακατέργαστη προσωρινή μνήμη byte, ακολουθώντας τη δομή MS-EMF §2.3.1.6: έναν πίνακα κορυφών με γωνίες RGBA 16-bit, ακολουθούμενο από μια λίστα ορθογωνίων που η καθεμία αναφέρεται σε δύο από αυτές τις κορυφές. Για το GRADIENT_FILL_RECT_H, τα χρώματα σαρώνουν από αριστερά προς τα δεξιά κατά μήκος της οριζόντιας μέσης γραμμής του ορθογωνίου· για το GRADIENT_FILL_RECT_V, σαρώνουν από πάνω προς τα κάτω κατά μήκος της κάθετης μέσης γραμμής. Και στις δύο περιπτώσεις τα δύο χρώματα γωνίας και οι προβεβλημένες συντεταγμένες του ορθογωνίου πηγαίνουν απευθείας στο THotPDF.RegisterAxialGradient, το οποίο επιστρέφει ένα όνομα προτύπου, και η σελίδα σχεδιάζει το ορθογώνιο και το γεμίζει μέσω αυτού του προτύπου (SetFillPattern) αντί για μια επίπεδη κλήση SetRGBFillColor, ώστε μια κεφαλίδα σε στυλ υπολογιστικού φύλλου με ζώνες ή η περιοχή σχεδίασης διαβάθμισης ενός γραφήματος να διατηρεί την ανάμειξή της αντί να καταρρέει σε ένα μέσο χρώμα

Η λειτουργία τριγώνων Gouraud είναι το ειλικρινές κενό. Όταν το πεδίο ulMode της εγγραφής αναφέρει GRADIENT_FILL_TRIANGLE, το VEMRGradientFill το αναγνωρίζει, καταγράφει ότι η λειτουργία τριγώνων δεν έχει υλοποιηθεί ακόμη, και παραλείπει το ορθογώνιο αντί να μαντέψει μια προσέγγιση δύο χρωμάτων. Η παρεμβολή ανά κορυφή, ανά pixel σε ένα αυθαίρετο πλέγμα τριγώνων δεν ανάγεται σε αξονική ή ακτινική σκίαση δύο σταδίων, και η ορθή έκφρασή της θα σήμαινε την εκπομπή σκίασης πλέγματος PDF Type 4 ή Type 5, την ίδια οικογένεια σκίασης που ο αποδότης σελίδας του HotPDF αφήνει επίσης αζωγράφιστη όταν διαβάζει πίσω ένα PDF. Δύο ασύνδετες διαδρομές κώδικα καταλήγουν στο ίδιο όριο: οι σκιάσεις πλέγματος είναι το κενό τόσο στην πλευρά της εγγραφής όσο και στην πλευρά της ανάγνωσης, και ένα διάγραμμα προέλευσης που χρησιμοποιεί τρίγωνα Gouraud για μια ομαλή ακτινική λάμψη επιστρέφει σε ό,τι ήταν η τελευταία συμπαγής βούρτσα, όχι σε μια αποδοθείσα προσέγγιση

Τα βουρτσάκια hatch γίνονται πρότυπα παράθεσης, όχι επίπεδο γκρι

Ένα βουρτσάκι hatch του GDI διατηρεί την υφή του στο PDF επειδή το THPDFWmf.SetBrushColor ελέγχει το CurrentBrush.lbStyle για BS_HATCHED προτού καταφύγει ποτέ σε συμπαγή γέμιση, δρομολογώντας αυτή την περίπτωση στο SetHatchBrushPattern αντ' αυτού. Αυτή η μέθοδος γράφει μια ροή περιεχομένου PDF 8-επί-8 μονάδων από τελεστές γραμμής με περίγραμμα, m, l, και S, επιλεγμένους από το στυλ hatch του GDI: μία μόνο οριζόντια ή κάθετη γραμμή για τα HS_HORIZONTAL και HS_VERTICAL, τρεις παράλληλες διαγώνιες για τα HS_FDIAGONAL και HS_BDIAGONAL, και οι συνδυασμοί οριζόντιου-συν-κάθετου ή αμφότερων-διαγώνιων για τα HS_CROSS και HS_DIAGCROSS. Το THotPDF.RegisterTilingPattern καταχωρεί αυτή τη ροή περιεχομένου ως έγχρωμο πρότυπο παράθεσης (PaintType 1, ISO 32000-1 §8.7.3.1) με XStep και YStep 8 μονάδων, και η σελίδα γεμίζει μέσω SetFillPattern με τον ίδιο τρόπο που κάνει και μια αξονική σκίαση. Ένα κάτοψη CAD ή ένα μηχανολογικό σχέδιο που βασίζεται σε γεμίσεις hatch για να διακρίνει υλικά διατηρεί αυτή τη οπτική γλώσσα στο PDF αντί να χάνει κάθε περιοχή σε πανομοιότυπο γκρι

Δεν κερδίζει κάθε βουρτσάκι αυτή τη μεταχείριση, και το κενό αξίζει να το γνωρίζετε πριν κυκλοφορήσει μια εισαγωγή CAD. Το EMR_CREATEDIBPATTERNBRUSHPT, η εγγραφή για μια προσαρμοσμένη βούρτσα προτύπου εικόνας bitmap αντί για ένα από τα έξι έτοιμα στυλ hatch του GDI, απλώς καταχωρεί τον χειριστή της ώστε οι μετέπειτα εγγραφές SELECTOBJECT και DELETEOBJECT να παραμείνουν συνεπείς· το HotPDF δεν εκθέτει ακόμη μια διοχέτευση πόρου PDF Pattern για αυθαίρετες εικόνες πλακιδίων, οπότε η επιλογή αυτού του βουρτσιού καταλήγει σε εναλλακτική συμπαγούς χρώματος αντί για την υφή προέλευσης. Αν μια γέμιση αποδίδεται επίπεδη ενώ το πρωτότυπο σαφώς χρησιμοποιούσε μια επαναλαμβανόμενη υφή εικόνας, το βουρτσάκι προέλευσης είναι σχεδόν σίγουρα ένα προσαρμοσμένο πρότυπο DIB παρά ένα τυπικό hatch, και αυτή είναι η μία περίπτωση που αξίζει να ελεγχθεί πρώτα με το χέρι. Η ρύθμιση μιας εισαγωγής για ένα τέτοιο σχέδιο εξακολουθεί να περνά από το ίδιο αντικείμενο επιλογών:

var
  Pdf: THotPDF;
  Drawing: TMetafile;
  Options: THPDFEmfOptions;
begin
  Pdf := THotPDF.Create(nil);
  Drawing := TMetafile.Create;
  Options := THPDFEmfOptions.Create;
  try
    Drawing.LoadFromFile('floor-plan.emf');
    Options.Assign(Pdf.EmfOptions);   // start from the document-wide defaults
    Options.Redraw := False;          // interpret the original EMF bytes, no GDI re-record pass
    Options.ShowNullBrush := True;    // keep explicitly unfilled CAD regions visible
    Options.UseFrame := True;         // clip output to the frame the EMF header declares
    Pdf.FileName := 'floor-plan.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
    Pdf.EndDoc;
  finally
    Options.Free;
    Drawing.Free;
    Pdf.Free;
  end;
end;

Τι εμποδίζει ένα κακοσχηματισμένο metafile να αλλοιώσει τη σελίδα;

Η απάντηση του HotPDF είναι μια μοναδική πύλη στην κορυφή του ExecuteRecord αντί για έναν αμυντικό έλεγχο που επαναλαμβάνεται στον καθένα από τους περίπου ογδόντα χειριστές εγγραφών του. Μια αγκύλη διαδρομής GDI, που ανοίγει με EMR_BEGINPATH και κλείνει με EMR_ENDPATH ή EMR_ABORTPATH, παρακολουθείται από μια ιδιωτική ιδιότητα PathContinue υποστηριζόμενη από το πεδίο FPathContinue. Ενώ αυτή η αγκύλη είναι ανοιχτή, το ExecuteRecord αφήνει να περάσουν μόνο εγγραφές κατασκευής διαδρομής, τις παραλλαγές μετακίνησης, γραμμής, πολυγραμμής, πολυγώνου, πολυμπεζιέ, και πολυσχεδίασης, συν το CLOSEFIGURE και ένα μικρό σύνολο εγγραφών μετασχηματισμού και κατάστασης DC όπως τα SETWORLDTRANSFORM, SAVEDC, και RESTOREDC. Κάθε άλλος τύπος εγγραφής που φτάνει στο ExecuteRecord ενώ η αγκύλη είναι ανοιχτή, ένα περιπλανώμενο EXTTEXTOUT ή ένα blit bitmap για παράδειγμα, απορρίπτεται κεντρικά με ένα μοναδικό Exit τη στιγμή που φτάνει

Αυτή η πύλη υπάρχει επειδή μια αγκύλη διαδρομής σε ένα χειρόγραφο, εργαλειοπαραγόμενο, ή απλώς αλλοιωμένο metafile δεν είναι εγγυημένο ότι θα περιέχει μόνο ό,τι θα έβαζε ένα καλά σχηματισμένο αρχείο μεταξύ των εγγραφών ανοίγματος και κλεισίματός του. Μια εγγραφή εξόδου κειμένου που θα προσγειωνόταν μεταξύ EMR_BEGINPATH και EMR_ENDPATH θα, χωρίς πύλη, είτε θα μόλυνε τη γεωμετρία διαδρομής υπό κατασκευή είτε θα εξέπεμπε έναν τελεστή εμφάνισης κειμένου PDF στη μέση μιας ακολουθίας που υποτίθεται ότι είναι καθαρή κατασκευή διαδρομής, και τα δύο είδη αποτυχίας είναι από αυτά που εμφανίζονται σε μία κακοσχηματισμένη είσοδο από ένα εργαλείο τρίτου μέρους, όχι σε κάτι που τυχαίνει να καλύπτει μια κανονική σουίτα δοκιμών. Η συγκέντρωση του ελέγχου στο ExecuteRecord σημαίνει ότι οι μεμονωμένοι χειριστές VEMR* δεν χρειάζεται ο καθένας να αμυνθεί ενάντια στο να κληθεί τη λάθος στιγμή· η πύλη το αποφασίζει μία φορά, πριν την αποστολή, αντί για ογδόντα φορές μετά από αυτήν

Τοποθετώντας ένα διανυσματικό γράφημα δίπλα σε κείμενο και εικόνες σε μία σελίδα

Μια σελίδα αναφοράς σπάνια περιέχει μόνο ένα γράφημα, και το ShowMetafile συνθέτει με τους άλλους τελεστές σελίδας του HotPDF ακριβώς όπως κάθε άλλη κλήση σχεδίασης. Μια επικεφαλίδα σχεδιασμένη με TextOut, ένα ραβδόγραμμα γεμάτο με hatch εισαγόμενο ως EMF, και ένα λογότυπο τοποθετημένο με ShowImage μπορούν όλα να προσγειωθούν στην ίδια σελίδα στην ίδια ροή περιεχομένου, καθένα διατηρώντας τη γηγενή του πιστότητα, το μοτίβο σύνθεσης που καλύπτεται στον οδηγό του HotPDF για τη διάταξη κειμένου, γραμματοσειρών, και εικόνων σε μια αναφορά:

Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart);   // hatch-filled bars, still vector
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);

Ο διερμηνέας EMF και WMF, τα πρότυπα αξονικής σκίασης που καταχωρεί για διαβαθμισμένες γεμίσεις, και η αντιστοίχιση προτύπων παράθεσης για βουρτσάκια hatch που περιγράφονται εδώ, όλα διατίθενται ως μέρος του τυπικού εξαρτήματος HotPDF για Delphi και C++Builder, μιας γηγενούς βιβλιοθήκης VCL χωρίς καμία εξωτερική εξάρτηση DLL για τίποτα από αυτά