مقاله فنی

ورود EMF به PDFlibPas: قواعد PolyDraw و Polyline و Bezier

PDFlibPas، کتابخانهٔ PDF losLab برای Delphi، رکوردهای Poly* را در EMF با پیروی از تعریف هر رکورد در [MS-EMF] به pathهای PDF تبدیل می‌کند: EMR_POLYBEZIER سی و دو بیتی از نقطهٔ 0 شروع می‌شود، polylineها باز می‌مانند و فقط stroke می‌شوند، PT_CLOSEFIGURE در EMR_POLYDRAW یک فلگ است، و هر شمارندهٔ نقطه نسبت به اندازهٔ رکورد چک می‌شود. این قواعد در v3.539.39 و v3.539.41 و v3.539.43 فرود آمدند. قبل از آن‌ها ممکن بود یک چارت گزارشی از ImportEMFFromFile بیرون بیاید با یک گوهٔ پرشده به‌جای خط روند، یک منحنی Bezier که به control point اشتباهی خم شده بود، یا یک کانتور بسته که ضلع آخرش غایب بود. هیچ‌کدام از این‌ها error بالا نمی‌دادند، و این قواعد برای هر converter ای از EMF به PDF در Delphi یا هر parser رکوردهای GDI هم صدق می‌کنند

چرا رکوردهای Poly* در EMF موقع تبدیل به PDF خراب می‌شوند؟

رکوردهای Poly* در EMF برای این خراب می‌شوند که هر کدام بخشی از معنای خود را بیرون از نقاطش حمل می‌کند: اینکه شکل باز است یا نه، اینکه از موقعیت فعلی شروع می‌شود یا نه، کدام pen و brush اعمال می‌شوند، و نقاط از کجای رکورد آغاز می‌شوند. یک enhanced metafile ضبطی از فراخوانی‌های GDI روی یک device context است، پس converter باید علاوه بر مختصات، state آن device context را هم دوباره اجرا کند. PDF هیچ device context ای ندارد. یک path دارد، یک current point داخل آن path، و یک عملگر نقاشی که بین stroke (S) و fill (f) و هر دو (B) تصمیم می‌گیرد. هر ناهماهنگی بین این دو مدل به یک تفاوت بی‌صدا در رندر تبدیل می‌شود

خانوادهٔ Poly* در دو عرض عرضه می‌شود. هر رکورد 32 بیتی مثل EMR_POLYLINE یک دوقلوی 16 بیتی مثل EMR_POLYLINE16 دارد که نقاط را به‌شکل جفت‌های SmallInt ذخیره می‌کند. GDI معمولاً وقتی همهٔ مختصات جا شوند نسخهٔ فشرده را ضبط می‌کند، پس handlerهای 32 بیتی یک converter می‌توانند سال‌ها خراب بمانند در حالی که نقشه‌های تست روزمره هرگز به آن‌ها نمی‌رسند. سریع‌ترین ممیزی این است که همان نقاط را از هر دو رکورد عبور دهی و pathهای حاصل را مقایسه کنی. رکوردهای این مقاله همه در گروه رکوردهای رسم [MS-EMF] هستند (2.3.5 Drawing Record Types)

رکوردشروع ازبسته؟موقعیت فعلی
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، نقطهٔ پایان. پس رکوردی با 7 نقطه دو سگمنت مکعبی می‌کشد: نقطهٔ 0 شروع است، 1 تا 3 سگمنت اول را می‌سازند و 4 تا 6 سگمنت دوم. handler 16 بیتی در PDFlibPas همین را از قبل درست انجام می‌داد. handler 32 بیتی گروه‌بندی را از نقطهٔ 0 شروع می‌کرد، پس نقطهٔ شروع به‌عنوان اولین control point مصرف می‌شد و هر سگمنت بعدی یکی جابه‌جا بود. منحنی همچنان رندر می‌شد، فقط منحنی اشتباه. از v3.539.41 هر دو عرض path را با m روی نقطهٔ 0 باز می‌کنند و بعد از آن به‌ازای هر سه‌تایی کامل یک c صادر می‌کنند

نمودار PDFlibPas از یک رکورد EMR_POLYBEZIER با هفت نقطه که نقطهٔ صفر path را با m باز می‌کند و نقاط یک تا سه و چهار تا شش هر کدام یک سگمنت مکعبی c می‌سازند؛ مقایسهٔ handler 32 بیتی اصلاح‌شده از v3.539.41 با گروه‌بندی قدیمی که نقطهٔ شروع را به‌عنوان control point مصرف می‌کرد
نقطهٔ 0 نقطهٔ شروع است و فقط سه‌تایی‌های کامل بعد از آن سگمنت مکعبی می‌شوند، پس یک PolyBezier هفت‌نقطه‌ای با m به‌علاوهٔ دو عملگر c رندر می‌شود

برای parser خودت: شمارنده‌ای که 1 به‌علاوهٔ مضربی از 3 نیست malformed است و نقاط اضافی انتهایی باید نادیده گرفته شوند نه اینکه به زور به منحنی دوخته شوند

PolyDraw: ‏PT_CLOSEFIGURE یک فلگ است نه یک نوع نقطه

در EMR_POLYDRAW، ‏PT_CLOSEFIGURE (مقدار 1) بتی است که با PT_LINETO (2) یا PT_BEZIERTO (4) ترکیب می‌شود، پس یک بایت نوع معتبر می‌تواند 3 یا 5 باشد. نوع نقطه همان بایت است با آن بیت ماسک‌شده، و فلگ یعنی شکل را بعد از سگمنتی که روی این نقطه تمام می‌شود ببند. handler قدیمی PDFlibPas بایت را در یک دستور case با مقادیر تکی مقایسه می‌کرد، پس نقاطی از نوع 3 و 5 به هیچ‌چیز نمی‌خوردند و کلاً رد می‌شدند. یک مستطیلی که با PolyDraw کشیده می‌شد ضلع بستنش را گم می‌کرد، و یک سه‌تایی Bezier که نقطهٔ آخرش فلگ را داشت همان نقطه را گم می‌کرد، که هر سه‌تایی بعدی را از ریتم می‌انداخت

از v3.539.39 نوع به‌شکل Types[i] and not PT_CLOSEFIGURE خوانده می‌شود و بستن فقط بعد از یک سگمنت کامل صادر می‌شود: بعد از خط برای یک PT_LINETO بسته‌شونده، و بعد از نقطهٔ سوم یک گروه Bezier. فایل malformed ای که فلگ را روی نقطهٔ اول یا دوم یک سه‌تایی ست می‌کند شکل را زودتر نمی‌بندد. دو فیکس مرتبط هم در همان release ارسال شد:

  • هر PT_MOVETO در EMR_POLYDRAW16 16 بیتی کل path را از نو شروع می‌کرد، پس رکوردی با سه شکل فقط آخری را نگه می‌داشت؛ حالا حرکت اول path را آغاز می‌کند و حرکت‌های بعدی subpath باز می‌کنند
  • رکورد PolyDraw ای که با PT_MOVETO شروع نمی‌شود از موقعیت فعلی آغاز می‌شود، همان‌طور که تعریف رکورد می‌گوید، به‌جای آنکه یک عملگر l یا c بدون m قبلی بنویسد
کالبدشکافی بایت نوع EMR_POLYDRAW در PDFlibPas که در آن PT_CLOSEFIGURE بیت فلگ صفر است که با OR به PT_LINETO یا PT_BEZIERTO تزریق می‌شود، پس بایت‌های نوع معتبر 3 و 5 باید قبل از dispatch با and not PT_CLOSEFIGURE ماسک شوند؛ دستور case قدیمی هر دو بایت را رد می‌کرد و شکل‌های بسته ضلع آخرشان را گم می‌کردند
فلگ بستن را قبل از dispatch ماسک کن و بستن را فقط بعد از یک خط کامل یا سه‌تایی Bezier صادر کن، وگرنه PolyDraw نقاط را بی‌صدا می‌اندازد

چرا polyline در EMF هرگز نباید در PDF پر شود؟

یک polyline در EMF هرگز نباید پر شود چون EMR_POLYLINE و EMR_POLYPOLYLINE شکل‌های بازی هستند که فقط با pen کشیده می‌شوند و پر کردن یک path باز در PDF آن را ضمنی می‌بندد. ISO 32000-1 §8.5.3 می‌گوید عملگرهای fill هر subpath بازی را قبل از نقاشی می‌بندند. پس converter ای که برای یک polyline سه‌نقطه‌ای B یا f صادر می‌کند یک مثلث پرشده به رنگ brush فعلی نقاشی می‌کند: همان گوهٔ پرشده زیر خط روند چارت. قبل از v3.539.41، PDFlibPas هر دو عرض polyline را با brush پر می‌کرد و رکورد 32 بیتی هم صریحاً بسته می‌شد. حالا هر دو عرض فقط با stroke تمام می‌شوند و تمایز GDI حفظ می‌شود: Polygon می‌بندد و پر می‌کند، Polyline هرگز

مقایسهٔ PDFlibPas بین یک polyline باز به شکل V خروجی‌گرفته از EMR_POLYLINE: converter درست path را با عملگر stroke یعنی S تمام می‌کند و brush انتخاب‌شده را نادیده می‌گیرد، در حالی که صادر کردن f یا B subpath باز را طبق ISO 32000-1 8.5.3 ضمنی می‌بندد و باگ گوهٔ پرشدهٔ چارت را نقاشی می‌کند
عملگر fill هر subpath بازی را قبل از نقاشی می‌بندد، پس polylineها باید با S تمام شوند بدون هیچ h یا f یا B روی subpath

PolylineTo از موقعیت فعلی شروع می‌شود

EMR_POLYLINETO از موقعیت فعلی از میان هر نقطهٔ رکورد می‌کشد، باز می‌ماند و موقعیت فعلی را روی آخرین نقطه می‌گذارد. handler قدیمی یک حالت خاص هم داشت که وقتی دو نقطهٔ اول y مشترک داشتند pen را خاموش می‌کرد، و هیچ‌چیز هرگز دوباره روشنش نمی‌کرد، پس هر رکورد بعدی در فایل کانتورش را از دست می‌داد. وضعیت pen به EMR_SELECTOBJECT و EMR_CREATEPEN تعلق دارد؛ handler یک رکورد رسم هیچ کاری به آن ندارد. آن حالت خاص در v3.539.41 حذف شد، و فرم تک‌نقطه‌ای رکورد هم دیگر از نقاط خودش بیرون نمی‌خواند (فیکس در v3.539.39)

نقاط PolyPolyline بعد از آرایهٔ شمارنده‌ها شروع می‌شوند

EMR_POLYPOLYLINE 32 بیتی nPolys شمارنده و بعد cptl نقطه ذخیره می‌کند و نقاط از بایت آفست 32 + nPolys * 4 آغاز می‌شوند. تله داخل RTL است: یونیت Windows ‏TEMRPolyPolyline را با aPolyCounts و aptl به‌شکل آرایه‌های تک‌عنصری declare می‌کند، پس aptl[0] فقط وقتی nPolys برابر 1 است اولین نقطه است. کدی که مستقیم روی aptl اندیس می‌زند مقدارهای شمارنده را به‌عنوان مختصات می‌خواند برای هر رکورد چندخطی. handler قدیمی PDFlibPas هم چک محدوده‌اش را روی همان layout غلط اندازه گرفته بود، پس رکوردهای چندخطی معتبر رد می‌شدند و تک‌خطی‌ها هیچ نمی‌کشیدند. از v3.539.41 PDFlibPas آرایهٔ نقاط را از آفست محاسبه‌شده پیدا می‌کند، همان‌طور که handler PolyPolygon آن همیشه می‌کرد، و هر polyline را به‌عنوان subpath باز خودش با یک stroke در انتها می‌کشد. در v3.539.43 دوقلوی 16 بیتی هم همین درمان را گرفت؛ آن یکی سگمنت‌به‌سگمنت می‌کشید که line joinها را می‌شکست و یک NULL_PEN انتخاب‌شده را نادیده می‌گرفت

pen و brush پیش‌فرض، و براکت‌های path

دو قاعدهٔ state فیکس‌های polyline در v3.539.43 را کامل می‌کنند:

  • یک device context تازهٔ GDI از قبل BLACK_PEN و WHITE_BRUSH انتخاب‌شده دارد، پس متافیلی که بدون هیچ EMR_SELECTOBJECT ای می‌کشد همچنان کانتورهای سیاه می‌کشد؛ converter قبلاً بدون pen و بدون fill شروع می‌کرد و برای چنین رکوردهایی n می‌نوشت (پایان path، بدون نقاشی)
  • داخل براکت BeginPath / ‏EndPath، یک Polyline نه از موقعیت فعلی استفاده می‌کند نه آن را به‌روز می‌کند، پس باید در اولین نقطه‌اش subpath جدید باز کند به‌جای اتصال به شکل قبلی، و تا وقتی براکت stroke یا fill نشده هیچ‌چیز نباید نقاشی شود

ساختن فایل تست EMF با TMetafileCanvas

سریع‌ترین راه برای چک کردن یک converter در برابر این قواعد این است که سه فراخوانی پرریسک را با TMetafileCanvas در یک enhanced metafile ضبط کنی. رسم زیر منحنی‌ها را با یک brush توخالی ضبط می‌کند و بعد عمداً یک brush زرد توپر برای polyline انتخاب می‌کند: converter درست باید برای polyline آن brush را نادیده بگیرد، پس هر زردی در PDF خروجی یک باگ است. PolyDraw هیچ wrapper ای در TCanvas ندارد، پس از طریق Windows API با handle بوم صدا زده می‌شود، با بایت‌های نوع 3 و 5 تا فلگ بستن ورزیده شود

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

procedure BuildPolyTestEmf(const FileName: string);
const
  // یک مربع بسته (3 = LINETO + CLOSEFIGURE)، بعد یک شکل Bezier بسته
  // که سه‌تایی کنترلی آخرش با 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 بیتی را ذخیره می‌کند. برای رسیدن به handlerهای 32 بیتی یا باید تولیدکننده‌ای داشته باشی که آن‌ها را بنویسد، یا رکوردها را دستی بسازی. فایل‌های دست‌ساز تلهٔ خودشان را دارند: ‏TMetafile.LoadFromStream در VCL استریم را فقط وقتی EMF می‌داند که طول باقی‌مانده اکیداً از TEnhMetaHeader 108 بایتی بیشتر باشد. یک EMF دست‌نویس حداقلی با header کوتاه، یا یک EMF خالی که دقیقاً 108 بایت است، WMF فرض می‌شود و با «Metafile is not valid» رد می‌شود. همیشه قبل از رکوردهای تستت header کامل 108 بایتی را با فیلدهای extension بنویس

وارد کردن EMF به PDF با PDFlibPas

PDFlibPas یک EMF را با ImportEMFFromFile یا ImportEMFFromStream وارد می‌کند که روی موفقیت یک image ID غیرصفر و روی شکست 0 برمی‌گردانند. ‏GeneralOptions = 0 همان path برداری را نگه می‌دارد که موضوع این مقاله است؛ 1 متافایل را به‌جای آن به بیت‌مپ rasterize می‌کند. ‏FontOptions = 1 فونت‌های متافایل را به‌شکل فونت‌های TrueType غیرجاسازی‌شده اضافه می‌کند. واریانت استریم قبل از load استریم را به موقعیت 0 برمی‌گرداند، پس استریمی بده که فقط متافایل را داشته باشد

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);    // بر حسب point
    // FontOptions 1 = اضافه کردن فونت‌ها به‌شکل TrueType غیرجاسازی‌شده
    // GeneralOptions 0 = import برداری، 1 = بیت‌مپ
    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 اندازهٔ قاب بر حسب point است
    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;

یک import برداری EMF به یک form XObject تبدیل می‌شود، پس GetPageContentToString فقط توالی save و transform و Do و restore را برمی‌گرداند. عملگرهای m و l و c و h و S که از رکوردهای Poly* تولید شده‌اند داخل استریم form XObject زندگی می‌کنند که فشرده است. برای ممیزی‌شان فایل ذخیره‌شده را در یک PDF object inspector دیکامپرس کن و استریم فرم را بخوان: برای فایل تست بالا باید ببینی polyline با S تمام می‌شود بدون هیچ h قبلش، در هر فلگ بستن در شکل‌های PolyDraw یک h هست، و روی هیچ‌کدام از این subpathها f یا B نیست. ‏DrawImage همچنین یک EMF واردشده را یکنواخت با کوچک‌ترین Width و Height مقیاس می‌کند، پس رسم نسبت ابعادش را نگه می‌دارد حتی اگر جعبه‌ای که می‌دهی با آن نخواند

برای تارگت‌های Free Pascal، ‏ساختن importer برداری EMF در PDFlibPas زیر Free Pascal را ببین؛ معناشناسی رکوردها هر جا که importer کامپایل شود یکسان است

parser EMF باید با شمارنده‌های نقطهٔ داخل فایل چطور رفتار کند؟

یک parser EMF باید هر شمارندهٔ نقطه را ورودی غیرقابل‌اعتماد بداند و قبل از کپی کردن حتی یک نقطه آن را با اندازهٔ رکورد بسنجد. ‏EnumEnhMetaFile فقط تضمین می‌کند nSize هر رکورد داخل فایل بماند. بررسی نمی‌کند که cptl با nSize سازگار باشد، پس handler ای که cptl نقطه را با Move کپی می‌کند وقتی شمارنده جعلی یا خراب است رکوردهای بعدی را می‌خواند، یا از انتهای متافایل رد می‌شود. از v3.539.39 PDFlibPas برای PolyDraw و PolyBezier و PolyBezierTo و Polyline و PolylineTo و Polygon در هر دو عرض، header ثابت به‌علاوهٔ شمارنده ضربدر بایت به‌ازای هر نقطه را با nSize می‌سنجد، با یک بایت اضافه به‌ازای هر نقطه برای بایت‌های نوع PolyDraw. برای رکوردهای PolyPoly شمارنده‌های هر شکل هم باید جمعشان از مجموع اعلام‌شده بیشتر نشود، و شکل‌های صفرنقطه‌ای رد می‌شوند

همین چک به اندازهٔ کافی کوتاه است که در parser خودت کپی کنی. این نسخه یک EMR_POLYPOLYLINE 32 بیتی را اعتبارسنجی می‌کند و اشاره‌گری به آرایهٔ واقعی نقاطش برمی‌گرداند:

uses
  Winapi.Windows;

// nil برمی‌گرداند مگر رکورد واقعاً نقاطی را که declare کرده نگه دارد.
// نقاط بعد از آرایهٔ شمارنده‌ها شروع می‌شوند: 32 + nPolys * 4 بایت داخل،
// نه در aptl[0] که RTL آن را به‌شکل آرایهٔ تک‌عنصری declare می‌کند
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 check را می‌اندازد
  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;

تست جا شدن نقاط اول اجرا می‌شود، پس قبل از اینکه حلقه آرایهٔ شمارنده‌ها را پیمایش کند معلوم است داخل رکورد است. محاسبات Int64 است چون nPolys * 4 و cptl * 8 در 32 بیت می‌توانند سرریز کنند و از مقایسه رد شوند

مرجع سریع: قواعد Poly* در EMF برای تبدیل EMF به PDF

  • EMR_POLYBEZIER: نقطهٔ 0 نقطهٔ شروع است؛ از نقطهٔ 1 به بعد سه‌تایی گروه کن؛ رکورد 32 بیتی در v3.539.41 فیکس شد
  • EMR_POLYLINE / ‏EMR_POLYPOLYLINE: شکل‌های باز، فقط stroke با S، هرگز h یا f یا B، چون fill در PDF subpathهای باز را می‌بندد
  • EMR_POLYLINETO: از موقعیت فعلی شروع کن، باز بمان، موقعیت فعلی را به‌روز کن، به وضعیت pen دست نزن
  • EMR_POLYPOLYLINE 32 بیتی: نقاط از بایت 32 + nPolys * 4 شروع می‌شوند، نه از aptl[0]
  • EMR_POLYDRAW: ‏PT_CLOSEFIGURE را قبل از dispatch ماسک کن، بعد از سگمنت کامل ببند، وقتی نقطهٔ اول PT_MOVETO نیست از موقعیت فعلی شروع کن
  • وضعیت پیش‌فرض device context ‏BLACK_PEN به‌علاوهٔ WHITE_BRUSH است؛ نسخهٔ v3.539.43 و بعد از آن به آن احترام می‌گذارند
  • داخل BeginPath / ‏EndPath، هر polyline subpath خودش را باز می‌کند و تا وقتی براکت مصرف نشده هیچ‌چیز نقاشی نمی‌شود
  • هر cptl / ‏cpts را قبل از کپی نقاط در محاسبات 64 بیتی با nSize اعتبارسنجی کن
  • EMFهای تست دست‌ساز به header کامل 108 بایتی نیاز دارند، وگرنه TMetafile.LoadFromStream آن‌ها را WMF می‌خواند

اگر گزارش‌هایت از کامپوننت دیگری رد می‌شوند، همین معناشناسی رکورد صادق است؛ ‏import برداری EMF و WMF در HotPDF توضیح می‌دهد آن کامپوننت چطور brushهای gradient و hatch را به patternهای PDF تبدیل می‌کند، و گرافیک برداری و shaderها و gradientها در PDFlibPas کشیدن همان شکل‌ها را مستقیم با API کتابخانه به‌جای واسطهٔ متافایل پوشش می‌دهد

PDFlibPas نسخهٔ v3.539.43 و بعدتر همهٔ قواعد بالا را شامل می‌شوند. جزئیات و دانلود نسخهٔ آزمایشی در صفحهٔ محصول PDFlibPas Delphi PDF library در دسترس است