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

Εισαγωγή EMF στο PDFlibPas: PolyDraw, Polyline, Bezier

Το PDFlibPas, η βιβλιοθήκη PDF της losLab για Delphi, μετατρέπει τα EMF records της οικογένειας Poly* σε διαδρομές PDF τηρώντας τον ορισμό κάθε record στο [MS-EMF]: ένα 32-bit EMR_POLYBEZIER ξεκινά από το σημείο 0, τα polylines μένουν ανοιχτά και παίρνουν μόνο stroke, το PT_CLOSEFIGURE στο EMR_POLYDRAW είναι flag, και κάθε πλήθος σημείων ελέγχεται σε σχέση με το μέγεθος του record. Οι κανόνες αυτοί μπήκαν σε τρεις εκδόσεις, v3.539.39, v3.539.41 και v3.539.43. Πριν από αυτά, ένα γράφημα αναφοράς μπορούσε να βγει από το ImportEMFFromFile με μια γεμάτη σφήνα εκεί που έπρεπε να είναι γραμμή τάσης, μια καμπύλη Bezier λυγισμένη προς λάθος control point, ή ένα κλειστό περίγραμμα που έλειπε την τελευταία του πλευρά. Κανένα από αυτά δεν πέταγε σφάλμα, και οι κανόνες ισχύουν για οποιονδήποτε μετατροπέα Delphi από EMF σε PDF ή parser GDI records

Γιατί πηγαίνουν στραβά τα EMF records Poly* στη μετατροπή σε PDF;

Τα EMF records Poly* πηγαίνουν στραβά επειδή το καθένα κουβαλά μέρος της σημασίας του έξω από τα σημεία του: αν το σχήμα είναι ανοιχτό, αν ξεκινά από την τρέχουσα θέση, ποια pen και ποιο brush ισχύουν, και πού μέσα στο record αρχίζουν τα σημεία. Ένα enhanced metafile είναι η καταγραφή κλήσεων GDI πάνω σε ένα device context, οπότε ένας converter πρέπει να ξαναπαίξει την κατάσταση εκείνου του device context εκτός από τις συντεταγμένες. Το PDF δεν έχει device context. Έχει μια διαδρομή, ένα τρέχον σημείο μέσα της, και έναν τελεστή ζωγραφικής που αποφασίζει ανάμεσα σε stroke (S), fill (f) και τα δύο (B). Κάθε ασυμφωνία των δύο μοντέλων γίνεται αθόρυβη διαφορά απεικόνισης

Η οικογένεια Poly* έρχεται επίσης σε δύο πλάτη. Κάθε 32-bit record όπως το EMR_POLYLINE έχει ένα 16-bit δίδυμο όπως το EMR_POLYLINE16 που αποθηκεύει τα σημεία ως ζεύγη SmallInt. Το GDI συνήθως γράφει τη συμπαγή μορφή όταν χωράει κάθε συντεταγμένη, οπότε οι 32-bit handlers ενός converter μπορούν να μένουν λάθος για χρόνια ενώ τα καθημερινά test σχέδια δεν τους φτάνουν ποτέ. Ο γρηγορότερος έλεγχος είναι να περάσετε τα ίδια σημεία και από τα δύο records και να συγκρίνετε τις προκύπτουσες διαδρομές. Τα records που καλύπτονται εδώ ανήκουν όλα στην ομάδα drawing records του [MS-EMF] (2.3.5 Drawing Record Types)

RecordΞεκινά απόΚλείνει;Τρέχουσα θέση
EMR_POLYBEZIERΣημείο 0ΌχιΔεν χρησιμοποιείται, δεν ενημερώνεται
EMR_POLYLINEΣημείο 0Όχι (μόνο pen)Δεν χρησιμοποιείται, δεν ενημερώνεται
EMR_POLYLINETOΤρέχουσα θέσηΌχι (μόνο pen)Χρησιμοποιείται και ενημερώνεται
EMR_POLYPOLYLINEΠρώτο σημείο κάθε polylineΌχι (μόνο pen)Δεν χρησιμοποιείται, δεν ενημερώνεται
EMR_POLYDRAWΠρώτο PT_MOVETO, ή τρέχουσα θέσηΜόνο όπου είναι ορισμένο το PT_CLOSEFIGUREΧρησιμοποιείται και ενημερώνεται

Πού ξεκινά στην πραγματικότητα μια καμπύλη EMR_POLYBEZIER;

Μια καμπύλη EMR_POLYBEZIER ξεκινά από το σημείο 0, και μόνο τα σημεία από τον δείκτη 1 και μετά ομαδοποιούνται ανά τρία ως control point, control point, end point. Ένα record με 7 σημεία σχεδιάζει λοιπόν δύο κυβικά τμήματα: το 0 είναι η αρχή, τα 1 έως 3 σχηματίζουν το πρώτο τμήμα, τα 4 έως 6 το δεύτερο. Ο 16-bit handler στο PDFlibPas το έκανε ήδη αυτό. Ο 32-bit handler ξεκινούσε την ομαδοποίηση από το σημείο 0, οπότε το σημείο εκκίνησης καταναλώνονταν ως πρώτο control point και κάθε επόμενο τμήμα ολίσθαινε κατά ένα. Η καμπύλη αποδιδόταν, απλώς η λάθος. Από το v3.539.41 και οι δύο πλάτες ανοίγουν τη διαδρομή με m στο σημείο 0 και εκπέμπουν ένα c ανά πλήρη τριάδα μετά

Διάγραμμα PDFlibPas ενός record EMR_POLYBEZIER με επτά σημεία όπου το σημείο μηδέν ανοίγει τη διαδρομή με m και τα σημεία ένα έως τρία και τέσσερα έως έξι σχηματίζουν από ένα κυβικό τμήμα c, σε αντίθεση του διορθωμένου 32-bit handler από το v3.539.41 με την παλιά ομαδοποίηση που κατανάλωνε το σημείο εκκίνησης ως control point
Το σημείο 0 είναι το σημείο εκκίνησης και μόνο οι πλήρεις τριάδες μετά από αυτό γίνονται κυβικά τμήματα, οπότε ένα PolyBezier επτά σημείων αποδίδεται ως ο τελεστής m συν δύο c

Για τον δικό σας parser: ένα πλήθος που δεν είναι 1 συν πολλαπλάσιο του 3 είναι κακοδιατυπωμένο, και τα σημεία στο τέλος πρέπει να αγνοηθούν αντί να ραφτούν σε μια καμπύλη

PolyDraw: το PT_CLOSEFIGURE είναι flag, όχι τύπος σημείου

Στο EMR_POLYDRAW, το PT_CLOSEFIGURE (τιμή 1) είναι ένα bit που συνδυάζεται με το PT_LINETO (2) ή το PT_BEZIERTO (4), οπότε ένα έγκυρο byte τύπου μπορεί να είναι 3 ή 5. Ο τύπος του σημείου είναι το byte με εκείνο το bit αποκομμένο με mask, και το flag σημαίνει κλείσε το σχήμα μετά το τμήμα που τελειώνει σε αυτό το σημείο. Ο παλιός handler του PDFlibPas ταίριαζε το byte με μονές τιμές σε μια εντολή case, οπότε σημεία τύπου 3 και 5 δεν ταίριαζαν σε τίποτα και παραλείπονταν εντελώς. Ένα ορθογώνιο σχεδιασμένο με PolyDraw έχανε την κλείσιμη πλευρά του, και μια τριάδα Bezier του οποίου το τελευταίο σημείο κρατούσε το flag έχανε εκείνο το σημείο, που έσπρωχνε κάθε επόμενη τριάδα εκτός συγχρονισμού

Από το v3.539.39 ο τύπος διαβάζεται ως Types[i] and not PT_CLOSEFIGURE, και το κλείσιμο εκπέμπεται μόνο μετά από πλήρες τμήμα: μετά τη γραμμή για ένα κλειστό PT_LINETO, και μετά το τρίτο σημείο μιας ομάδας Bezier. Ένα κακοδιατυπωμένο αρχείο που θέτει το flag στο πρώτο ή δεύτερο σημείο μιας τριάδας δεν κλείνει το σχήμα πρόωρα. Δύο συγγενικές διορθώσεις βγήκαν στην ίδια έκδοση:

  • Κάθε PT_MOVETO στο 16-bit EMR_POLYDRAW16 ξεκινούσε από την αρχή όλη τη διαδρομή, οπότε ένα record με τρία σχήματα κρατούσε μόνο το τελευταίο· τώρα η πρώτη κίνηση ξεκινά τη διαδρομή και οι επόμενες ανοίγουν subpaths
  • Ένα record PolyDraw που δεν αρχίζει με PT_MOVETO ξεκινά από την τρέχουσα θέση, όπως λέει ο ορισμός του record, αντί να γράψει τελεστή l ή c χωρίς προηγούμενο m
Ανατομία του byte τύπου ενός EMR_POLYDRAW στο PDFlibPas όπου το PT_CLOSEFIGURE είναι το flag bit μηδέν που OR-άρεται μέσα στο PT_LINETO ή το PT_BEZIERTO, οπότε τα έγκυρα bytes τύπου 3 και 5 πρέπει να καλυφθούν με mask με and not PT_CLOSEFIGURE πριν τη διακλάδωση· η παλιά εντολή case παρέλειπε και τα δύο bytes και τα κλειστά σχήματα έχαναν την τελευταία τους πλευρά
Αφαιρέστε το flag κλεισίματος με mask πριν τη διακλάδωση και εκπέμψτε το κλείσιμο μόνο μετά από ολοκληρωμένη γραμμή ή τριάδα Bezier, αλλιώς το PolyDraw χάνει αθόρυβα σημεία

Γιατί δεν πρέπει ποτέ να γεμίζει ένα EMF polyline στο PDF;

Ένα EMF polyline δεν πρέπει ποτέ να γεμίζει γιατί τα EMR_POLYLINE και EMR_POLYPOLYLINE είναι ανοιχτά σχήματα που σχεδιάζονται μόνο με την pen, και το γέμισμα μιας ανοιχτής διαδρομής στο PDF την κλείνει σιωπηλά. Το ISO 32000-1 §8.5.3 λέει ότι οι τελεστές fill κλείνουν κάθε ανοιχτό subpath πριν το ζωγραφίσουν. Ένας converter που εκπέμπει B ή f για ένα polyline τριών σημείων ζωγραφίζει λοιπόν ένα γεμάτο τρίγωνο στο τρέχον χρώμα του brush: τη γεμάτη σφήνα κάτω από τη γραμμή τάσης ενός γραφήματος. Πριν το v3.539.41, το PDFlibPas γέμιζε και τις δύο πλάτες polyline με το brush, και το 32-bit record κλεινόταν επιπλέον ρητά. Σήμερα και οι δύο πλάτες τελειώνουν μόνο με stroke, και η διάκριση του GDI διατηρείται: το Polygon κλείνει και γεμίζει, το Polyline ποτέ

Σύγκριση PDFlibPas ενός ανοιχτού polyline σε σχήμα V που εξάγεται από EMR_POLYLINE: ένας σωστός converter τελειώνει τη διαδρομή με τον τελεστή stroke S και αγνοεί το επιλεγμένο brush, ενώ η εκπομπή f ή B κλείνει σιωπηλά το ανοιχτό subpath κατά το ISO 32000-1 8.5.3 και ζωγραφίζει το bug της γεμάτης σφήνας στο γράφημα
Ένας τελεστής fill κλείνει κάθε ανοιχτό subpath πριν το ζωγραφίσει, οπότε τα polylines πρέπει να τελειώνουν σε S χωρίς h, f ή B στο subpath

Το PolylineTo ξεκινά από την τρέχουσα θέση

Το EMR_POLYLINETO σχεδιάζει από την τρέχουσα θέση μέσα από κάθε σημείο του record, μένει ανοιχτό, και αφήνει την τρέχουσα θέση στο τελευταίο σημείο. Ο παλιός handler περιείχε επίσης μια ειδική περίπτωση που έσβηνε την pen όταν τα δύο πρώτα σημεία μοιράζονταν συντεταγμένη y, και τίποτα δεν την ξανά άναβε, οπότε κάθε επόμενο record του αρχείου έχανε το περίγραμμά του. Η κατάσταση της pen ανήκει στο EMR_SELECTOBJECT και στο EMR_CREATEPEN· ένας handler drawing record δεν έχει καμία δουλειά να την αλλάζει. Εκείνη η ειδική περίπτωση αφαιρέθηκε στο v3.539.41, και η μορφή του record με ένα μόνο σημείο δεν διαβάζει πια πέρα από τα δικά του σημεία (διορθώθηκε στο v3.539.39)

Τα σημεία του PolyPolyline αρχίζουν μετά τον πίνακα counts

Το 32-bit EMR_POLYPOLYLINE αποθηκεύει nPolys πλήθη και μετά cptl σημεία, και τα σημεία αρχίζουν στο byte offset 32 + nPolys * 4. Η παγίδα είναι στο RTL: η unit Windows δηλώνει το TEMRPolyPolyline με aPolyCounts και aptl ως πίνακες ενός στοιχείου, οπότε το aptl[0] είναι το πρώτο σημείο μόνο όταν το nPolys είναι 1. Κώδικας που δεικτοδοτεί απευθείας το aptl διαβάζει τιμές πλήθους ως συντεταγμένες σε κάθε record πολλών γραμμών. Ο παλιός handler του PDFlibPas υπολόγιζε επιπλέον τον έλεγχο ορίων πάνω σε αυτή τη λάθος διάταξη, οπότε έγκυρα records πολλών γραμμών απορρίπτονταν και μονόγραμμα δεν σχεδίαζαν τίποτα. Από το v3.539.41 το PDFlibPas εντοπίζει τον πίνακα σημείων από το υπολογισμένο offset, όπως έκανε πάντα ο handler του PolyPolygon, και σχεδιάζει κάθε polyline ως δικό του ανοιχτό subpath με ένα stroke στο τέλος. Στο v3.539.43 το 16-bit δίδυμο πήρε την ίδια μεταχείριση· σχεδίαζε τμήμα ανά τμήμα, που έσπαγε τις ενώσεις γραμμών και αγνοούσε επιλεγμένο NULL_PEN

Η προεπιλεγμένη pen και brush, και οι αγκύλες path

Δύο κανόνες κατάστασης συμπληρώνουν τις διορθώσεις polyline στο v3.539.43:

  • Ένα φρέσκο device context του GDI έχει ήδη επιλεγμένα BLACK_PEN και WHITE_BRUSH, οπότε ένα metafile που σχεδιάζει χωρίς κανένα EMR_SELECTOBJECT εξακολουθεί να σχεδιάζει μαύρα περιγράμματα· ο converter ξεκινούσε χωρίς pen και χωρίς fill και έγραφε n (τέλος διαδρομής, καμία ζωγραφιά) για τέτοια records
  • Μέσα σε μια αγκύλη BeginPath / EndPath, ένα Polyline ούτε χρησιμοποιεί ούτε ενημερώνει την τρέχουσα θέση, οπότε πρέπει να ανοίξει νέο subpath στο πρώτο του σημείο αντί να συνδεθεί με το προηγούμενο σχήμα, και τίποτα δεν επιτρέπεται να ζωγραφιστεί μέχρι η αγκύλη να πάρει stroke ή fill

Χτίσιμο ενός αρχείου δοκιμής EMF με TMetafileCanvas

Ο γρηγορότερος τρόπος να ελέγξετε έναν converter απέναντι σε αυτούς τους κανόνες είναι να καταγράψετε τις τρεις επικίνδυνες κλήσεις σε ένα enhanced metafile με το TMetafileCanvas. Το σχέδιο παρακάτω καταγράφει τις καμπύλες με κούφιο brush και μετά επιλέγει σκόπιμα ένα συμπαγές κίτρινο brush για το polyline: ένας σωστός converter πρέπει να αγνοήσει εκείνο το brush για το polyline, οπότε κάθε κίτρινο στο PDF εξόδου είναι bug. Το PolyDraw δεν έχει wrapper σε TCanvas, οπότε καλείται μέσω του Windows API με το handle του canvas, με bytes τύπου 3 και 5 για να δοκιμαστεί το flag κλεισίματος

uses
  Winapi.Windows, System.Types, Vcl.Graphics;

procedure BuildPolyTestEmf(const FileName: string);
const
  // Ένα κλειστό τετράγωνο (3 = LINETO + CLOSEFIGURE), μετά ένα κλειστό
  // σχήμα Bezier του οποίου η τελευταία τριάδα control τελειώνει με 5 = BEZIERTO + CLOSEFIGURE
  DrawPts: array[0..7] of TPoint = (
    (X: 300; Y: 40), (X: 380; Y: 40), (X: 380; Y: 120), (X: 300; Y: 120),
    (X: 420; Y: 120), (X: 440; Y: 40), (X: 520; Y: 40), (X: 540; Y: 120));
  DrawTypes: array[0..7] of Byte = (
    PT_MOVETO, PT_LINETO, PT_LINETO, PT_LINETO or PT_CLOSEFIGURE,
    PT_MOVETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO or PT_CLOSEFIGURE);
var
  Mf: TMetafile;
  Canvas: TMetafileCanvas;
begin
  Mf := TMetafile.Create;
  try
    Mf.Enhanced := True;
    Mf.Width := 600;
    Mf.Height := 260;
    Canvas := TMetafileCanvas.Create(Mf, 0);
    try
      Canvas.Pen.Color := clNavy;
      Canvas.Pen.Width := 2;
      Canvas.Brush.Style := bsClear;    // μόνο περιγράμματα για τις καμπύλες
      // Το σημείο 0 είναι η αρχή· τα 1..3 και 4..6 είναι δύο κυβικά τμήματα
      Canvas.PolyBezier([Point(20, 120), Point(60, 20), Point(100, 220),
        Point(140, 120), Point(180, 20), Point(220, 220), Point(260, 120)]);
      PolyDraw(Canvas.Handle, DrawPts[0], DrawTypes[0], Length(DrawPts));
      // Ανοιχτό σχήμα V με επιλεγμένο συμπαγές brush: παίρνει stroke, δεν κλείνει
      // σε κίτρινο τρίγωνο
      Canvas.Brush.Style := bsSolid;
      Canvas.Brush.Color := clYellow;
      Canvas.Polyline([Point(20, 240), Point(120, 160), Point(220, 240)]);
    finally
      Canvas.Free;   // τελειώνει την καταγραφή
    end;
    Mf.SaveToFile(FileName);
  finally
    Mf.Free;
  end;
end;

Επειδή αυτές οι συντεταγμένες χωράνε σε SmallInt, το GDI κανονικά θα αποθηκεύσει τις 16-bit παραλλαγές. Για να φτάσετε τους 32-bit handlers χρειάζεστε έναν producer που τους γράφει, ή records που χτίζετε στο χέρι. Τα χειροποίητα αρχεία κουβαλούν τη δική τους παγίδα: το VCL TMetafile.LoadFromStream μεταχειρίζεται το stream ως EMF μόνο όταν το υπόλοιπο μήκος είναι αυστηρά μεγαλύτερο από το 108-byte TEnhMetaHeader. Ένα μίνιμαλ χειρόγραφο EMF με κοντό header, ή ένα κενό που είναι ακριβώς 108 bytes, παίρνεται για WMF και απορρίπτεται με "Metafile is not valid". Γράφετε πάντα το πλήρες header των 108 bytes, συμπεριλαμβανομένων των πεδίων επέκτασης, πριν τα test records σας

Εισαγωγή του EMF σε PDF με το PDFlibPas

Το PDFlibPas εισάγει ένα EMF με ImportEMFFromFile ή ImportEMFFromStream, που επιστρέφουν μη μηδενικό image ID σε επιτυχία και 0 σε αποτυχία. GeneralOptions = 0 κρατά τη διαδρομή vector που αφορά αυτό το άρθρο· το 1 ραστεροποιεί το metafile σε bitmap. FontOptions = 1 προσθέτει τις γραμματοσειρές του metafile ως μη ενσωματωμένες TrueType. Η παραλλαγή με stream γυρνά το stream στη θέση 0 πριν τη φόρτωση, οπότε περάστε ένα stream που περιέχει μόνο το metafile

uses
  System.SysUtils, PDFlibrary;

procedure EmfToPdf(const EmfFile, PdfFile: WideString);
var
  PDF: TPDFlib;
  ImageID: Integer;
  PageOps: AnsiString;
begin
  PDF := TPDFlib.Create;
  try
    PDF.SetOrigin(1);              // αρχή πάνω αριστερά για το DrawImage
    PDF.SetMeasurementUnits(0);    // μονάδες points
    // FontOptions 1 = προσθήκη γραμματοσειρών ως μη ενσωματωμένες TrueType
    // GeneralOptions 0 = εισαγωγή vector, 1 = bitmap
    ImageID := PDF.ImportEMFFromFile(EmfFile, 1, 0);
    if ImageID = 0 then
      raise Exception.Create('The metafile could not be imported');
    PDF.SelectImage(ImageID);
    // Για EMF, τα ImageWidth / ImageHeight είναι το μέγεθος πλαισίου σε points
    PDF.DrawImage(36, 36, PDF.ImageWidth, PDF.ImageHeight);

    // Η σελίδα μόνο καλεί τη φόρμα που εισήχθη: q ... cm /Name Do Q
    PageOps := PDF.GetPageContentToString;
    if Pos(AnsiString(' Do'), PageOps) = 0 then
      raise Exception.Create('Expected a form XObject invocation');

    if PDF.SaveToFile(PdfFile) <> 1 then
      raise Exception.Create('The PDF could not be saved');
  finally
    PDF.Free;
  end;
end;

Μια εισαγωγή vector EMF γίνεται form XObject, οπότε το GetPageContentToString επιστρέφει μόνο την ακολουθία save, transform, Do και restore. Οι τελεστές m, l, c, h και S που παράγονται από τα records Poly* μένουν στο stream του form XObject, το οποίο είναι συμπιεσμένο. Για να τους ελέγξετε, αποσυμπιέστε το αποθηκευμένο αρχείο σε ένα PDF object inspector και διαβάστε το stream της φόρμας: για το αρχείο δοκιμής παραπάνω θα πρέπει να δείτε το polyline να τελειώνει σε S χωρίς h μπροστά του, ένα h σε κάθε flag κλεισίματος στα σχήματα PolyDraw, και κανένα f ή B σε κανένα από αυτά τα subpaths. Το DrawImage κλιμακώνει επίσης ένα εισαγμένο EMF ομοιόμορφα με το μικρότερο από Width και Height, οπότε το σχέδιο κρατά τον λόγο διαστάσεων του ακόμα κι αν το πλαίσιο που περνάτε δεν ταιριάζει

Για στόχους Free Pascal, δείτε πώς χτίζει ο εισαγωγέας vector EMF του PDFlibPas κάτω από Free Pascal· η σημασιολογία των records είναι η ίδια όπου κι αν μεταγλωττίζεται ο εισαγωγέας

Πώς πρέπει να μεταχειρίζεται ένας EMF parser τα πλήθη σημείων από το αρχείο;

Ένας EMF parser πρέπει να μεταχειρίζεται κάθε πλήθος σημείων ως μη αξιόπιστη είσοδο και να το ελέγχει σε σχέση με το μέγεθος του record πριν αντιγράψει έστω ένα σημείο. Το EnumEnhMetaFile εγγυάται μόνο ότι κάθε nSize record μένει μέσα στο αρχείο. Δεν ελέγχει ότι το cptl συμφωνεί με το nSize, οπότε ένας handler που αντιγράφει cptl σημεία με Move θα διαβάσει τα επόμενα records, ή πέρα από το τέλος του metafile, όταν το πλήθος είναι πλαστό ή κατεστραμμένο. Από το v3.539.39 το PDFlibPas ελέγχει για τα PolyDraw, PolyBezier, PolyBezierTo, Polyline, PolylineTo και Polygon και στις δύο πλάτες το σταθερό header συν πλήθος επί bytes ανά σημείο σε σχέση με το nSize, με ένα extra byte ανά σημείο για τα bytes τύπου του PolyDraw. Για τα records PolyPoly τα πλήθη ανά σχήμα πρέπει επίσης να αθροίζουν το πολύ το δηλωμένο σύνολο, και τα σχήματα με μηδέν σημεία παραλείπονται

Ο ίδιος έλεγχος είναι αρκετά σύντομος για να τον αντιγράψετε στον δικό σας parser. Αυτή η εκδοχή επικυρώνει ένα 32-bit EMR_POLYPOLYLINE και επιστρέφει δείκτη στον πραγματικό του πίνακα σημείων:

uses
  Winapi.Windows;

// Επιστρέφει nil εκτός αν το record κρατά πραγματικά τα σημεία που δηλώνει.
// Τα σημεία αρχίζουν μετά τον πίνακα counts: 32 + nPolys * 4 bytes μέσα, όχι στο
// aptl[0], που το RTL δηλώνει ως πίνακα ενός στοιχείου
function PolyPolylinePoints(Rec: PEnhMetaRecord): PPoint;
var
  P: PEMRPolyPolyline;
  Count: PDWORD;
  PointsOffset, Total: Int64;
  I: Cardinal;
begin
  Result := nil;
  if (Rec^.iType <> EMR_POLYPOLYLINE) or (Rec^.nSize < 32) then
    Exit;
  P := PEMRPolyPolyline(Rec);
  if P^.nPolys = 0 then
    Exit;
  PointsOffset := 32 + Int64(P^.nPolys) * SizeOf(DWORD);
  if PointsOffset + Int64(P^.cptl) * SizeOf(TPoint) > Rec^.nSize then
    Exit;                         // πλαστό ή κομμένο πλήθος
  Total := 0;
  Count := @P^.aPolyCounts[0];    // βήμα με δείκτη: το [0..0] σπάει range checks
  for I := 1 to P^.nPolys do
  begin
    Inc(Total, Count^);
    Inc(Count);
  end;
  if Total > P^.cptl then
    Exit;                         // τα σχήματα διεκδικούν περισσότερα σημεία από όσα υπάρχουν
  Result := PPoint(NativeUInt(Rec) + NativeUInt(PointsOffset));
end;

Το test χωρητικότητας των σημείων τρέχει πρώτο, οπότε ο πίνακας counts είναι γνωστό ότι βρίσκεται μέσα στο record πριν ο βρόχος τον διασχίσει. Η αριθμητική είναι Int64 επειδή τα nPolys * 4 και cptl * 8 υπολογισμένα σε 32 bits μπορούν να κάνουν wrap-around και να περάσουν τη σύγκριση

Σύντομη αναφορά: κανόνες EMF Poly* για μετατροπή EMF σε PDF

  • EMR_POLYBEZIER: το σημείο 0 είναι το σημείο εκκίνησης· ομαδοποίηση από το σημείο 1 ανά τρία· διορθώθηκε για το 32-bit record στο v3.539.41
  • EMR_POLYLINE / EMR_POLYPOLYLINE: ανοιχτά σχήματα, stroke με S, ποτέ h, f ή B, επειδή το fill του PDF κλείνει τα ανοιχτά subpaths
  • EMR_POLYLINETO: εκκίνηση από την τρέχουσα θέση, μένει ανοιχτό, ενημερώνει την τρέχουσα θέση, δεν αλλάζει ποτέ την κατάσταση της pen
  • 32-bit EMR_POLYPOLYLINE: τα σημεία αρχίζουν στο byte 32 + nPolys * 4, όχι στο aptl[0]
  • EMR_POLYDRAW: αφαιρέστε το PT_CLOSEFIGURE με mask πριν τη διακλάδωση, κλείστε μετά το ολοκληρωμένο τμήμα, ξεκινήστε από την τρέχουσα θέση όταν το πρώτο σημείο δεν είναι PT_MOVETO
  • Η προεπιλεγμένη κατάσταση device context είναι BLACK_PEN συν WHITE_BRUSH· το v3.539.43 και μεταγενέστερα τη σέβονται
  • Μέσα σε BeginPath / EndPath, κάθε polyline ανοίγει το δικό του subpath και τίποτα δεν ζωγραφίζεται μέχρι η αγκύλη να χρησιμοποιηθεί
  • Επικυρώστε κάθε cptl / cpts σε σχέση με το nSize σε 64-bit αριθμητική πριν αντιγράψετε σημεία
  • Τα χειροποίητα test EMF θέλουν το πλήρες header των 108 bytes, αλλιώς το TMetafile.LoadFromStream τα διαβάζει ως WMF

Αν οι αναφορές σας περνούν από άλλο component, η ίδια σημασιολογία records ισχύει· το HotPDF EMF και WMF vector import καλύπτει πώς εκείνο το component μετατρέπει gradient και hatch brushes σε PDF patterns, και το vector graphics, shaders και gradients στο PDFlibPas καλύπτει το σχέδιο των ίδιων σχημάτων απευθείας με το API της βιβλιοθήκης αντί μέσω metafile

Το PDFlibPas v3.539.43 ή νεότερο περιλαμβάνει όλους τους παραπάνω κανόνες. Λεπτομέρειες και δοκιμαστικές λήψεις βρίσκονται στη σελίδα προϊόντος PDFlibPas Delphi PDF library