PDFlibPas คือ PDF library ของ losLab สำหรับ Delphi และมันแปลง EMF Poly* record เป็น path ของ PDF โดยยึดนิยามของแต่ละ record ใน [MS-EMF] เป๊ะ ๆ: EMR_POLYBEZIER 32 บิตเริ่มที่จุด 0 polyline ค้างเปิดไว้และถูก stroke เท่านั้น PT_CLOSEFIGURE ใน EMR_POLYDRAW เป็น flag และ point count ทุกตัวต้องเช็คกับขนาด record กฎพวกนี้มาถึงพร้อมกันใน v3.539.39 v3.539.41 และ v3.539.43 ก่อนหน้านั้นกราฟในรายงานที่ผ่าน ImportEMFFromFile อาจได้ wedge ตันโผล่มาแทนที่เส้น trend line เส้น Bezier โค้งเข้าหา control point ผิดตัว หรือ outline ที่ควรปิดขาดไปด้านสุดท้ายหนึ่งด้าน ทั้งหมดนี้ไม่เคยยก error ขึ้นมาเลย และกฎเดียวกันนี้ใช้กับ EMF to PDF converter ของ Delphi หรือ GDI record parser ตัวไหนก็ได้
ทำไม EMF Poly* record ถึงพังตอนแปลงเป็น PDF
EMF Poly* record พังกันเพราะแต่ละตัวแถมความหมายบางส่วนเอาไว้นอกกลุ่มจุดของมัน: รูปนั้นเปิดอยู่หรือปิด เริ่มจาก current position หรือเปล่า pen กับ brush ตัวไหนมีผล และพิกัดเริ่มต้นอยู่ตำแหน่งไหนใน record enhanced metafile คือการอัด GDI call ที่เคยเล่นกับ device context เอาไว้ converter จึงต้องเล่นซ้ำทั้งสถานะ device context และพิกัดด้วย ฝั่ง PDF ไม่มี device context ให้เล่น มีแค่ path กับ current point ข้างใน path นั้น และ painting operator ที่ตัดสินระหว่าง stroke (S) fill (f) กับทำทั้งสองอย่าง (B) ทุกความเหลื่อมล้ำระหว่างสองโมเดลนี้กลายเป็นความต่างตอน render แบบเงียบ ๆ
ตระกูล Poly* ยังมาเป็นสองความกว้าง record 32 บิตอย่าง EMR_POLYLINE มีฝาแฝด 16 บิตอย่าง EMR_POLYLINE16 ที่เก็บจุดเป็นคู่ SmallInt GDI มักอัดเวอร์ชันกะทัดรัดเมื่อพิกัดทุกตัวลงตัว handler ฝั่ง 32 บิตของ converter จึงผิดได้นานหลายปีโดยภาพทดสอบประจำวันไม่เคยไปถึงมัน วิธี audit ที่ไวที่สุดคือยัดจุดชุดเดิมเข้าทั้งสอง record แล้วเทียบ path ที่ได้ออกมา ส่วน record ที่บทความนี้คุ้มครองอยู่ในกลุ่ม drawing record ของ [MS-EMF] ทั้งหมด (2.3.5 Drawing Record Types)
| Record | เริ่มที่ | ปิดไหม | current position |
|---|---|---|---|
EMR_POLYBEZIER | จุด 0 | ไม่ | ไม่ใช้ ไม่อัปเดต |
EMR_POLYLINE | จุด 0 | ไม่ (ใช้แค่ pen) | ไม่ใช้ ไม่อัปเดต |
EMR_POLYLINETO | current position | ไม่ (ใช้แค่ pen) | ใช้และอัปเดต |
EMR_POLYPOLYLINE | จุดแรกของแต่ละ polyline | ไม่ (ใช้แค่ pen) | ไม่ใช้ ไม่อัปเดต |
EMR_POLYDRAW | PT_MOVETO ตัวแรก หรือ current position | เฉพาะตรงที่ PT_CLOSEFIGURE ถูกเซ็ต | ใช้และอัปเดต |
EMR_POLYBEZIER เริ่มวาดจากจุดไหนกันแน่
เส้น EMR_POLYBEZIER เริ่มที่จุด 0 และมีแค่จุดตั้งแต่ index 1 เป็นต้นไปที่จับจัดกลุ่มทีละสามเป็น control point control point end point record ที่มี 7 จุดจึงวาด cubic segment สองท่อน: จุด 0 คือจุดเริ่ม 1 ถึง 3 เป็นท่อนแรก 4 ถึง 6 เป็นท่อนที่สอง handler 16 บิตของ PDFlibPas ทำแบบนี้มาตลอด ส่วน handler 32 บิตเริ่มจัดกลุ่มที่จุด 0 จุดเริ่มเลยถูกกินไปเป็น control point ตัวแรก และ segment ที่เหลือทั้งหมดเลื่อนไปหนึ่งจุด เส้นยัง render ออกมาอยู่ดี แค่ผิดเส้น ตั้งแต่ v3.539.41 ทั้งสองความกว้างเปิด path ด้วย m ที่จุด 0 แล้วปล่อย c หนึ่งตัวต่อกลุ่มสามจุดที่ครบหลังจากนั้น
สำหรับ parser ของคุณเอง: count ที่ไม่ใช่ 1 บวกด้วยผลคูณของ 3 ถือเป็นไฟล์เสียหาย และจุดท้ายที่เกินควรทิ้งเฉย ๆ ไม่ใช่เย็บต่อเข้าไปในเส้น
PolyDraw: PT_CLOSEFIGURE เป็น flag ไม่ใช่ชนิดจุด
ใน EMR_POLYDRAW PT_CLOSEFIGURE (ค่า 1) เป็นบิตที่หรือรวมกับ PT_LINETO (2) หรือ PT_BEZIERTO (4) type byte ที่ถูกต้องจึงเป็น 3 หรือ 5 ได้ ชนิดของจุดคือ byte ตัวเดิมหลังกลบบิตนั้นทิ้ง ส่วน flag หมายความว่าปิดรูปหลัง segment ที่จบลงที่จุดนี้ handler เดิมของ PDFlibPas เทียบ byte กับค่าเดี่ยว ๆ ใน case statement จุดชนิด 3 กับ 5 จึงไม่เข้าเคสไหนเลยและถูกข้ามทิ้งทั้งดื้อ สี่เหลี่ยมที่วาดด้วย PolyDraw เลยขาดด้านปิดไปหนึ่งด้าน และกลุ่มสามจุด Bezier ที่จุดสุดท้ายแปะ flag ก็หายจุดนั้นไป ทำให้กลุ่มถัด ๆ ไปทั้งหมดเลื่อนจังหวะ
ตั้งแต่ v3.539.39 ชนิดถูกอ่านเป็น Types[i] and not PT_CLOSEFIGURE และคำสั่งปิดถูกปล่อยออกมาหลัง segment เต็มหนึ่งท่อนเท่านั้น: หลังเส้นสำหรับ PT_LINETO ที่ปิดรูป และหลังจุดที่สามของกลุ่ม Bezier ไฟล์เสียที่เซ็ต flag ที่จุดแรกหรือจุดสองของกลุ่มจะไม่ปิดรูปก่อนเวลา ของแก้ที่เกี่ยวเนื่องอีกสองชิ้นมาพร้อม release เดียวกัน:
- ทุก
PT_MOVETOในEMR_POLYDRAW1616 บิตเคยรีสตาร์ท path ทั้งเส้นใหม่ record ที่ถือรูปสามรูปเลยเหลือแค่รูปสุดท้าย ตอนนี้ move แรกเริ่ม path และ move ถัดไปเปิด subpath ใหม่ - record PolyDraw ที่ไม่ได้เริ่มด้วย
PT_MOVETOจะเริ่มที่ current position ตามที่นิยาม record บอก แทนที่จะเขียน operatorlหรือcออกมาโดยไม่มีmนำหน้า
ทำไม EMF polyline ถึงห้าม fill ใน PDF เด็ดขาด
EMF polyline ห้าม fill เด็ดขาด เพราะ EMR_POLYLINE กับ EMR_POLYPOLYLINE เป็นรูปเปิดที่วาดด้วย pen เท่านั้น และการ fill path เปิดใน PDF จะปิดมันโดยปริยาย ISO 32000-1 §8.5.3 ระบุว่า fill operator ปิด subpath เปิด ๆ ทิ้งก่อนลงสีเสมอ converter ที่ปล่อย B หรือ f ให้ polyline สามจุดจึงวาดสามเหลี่ยมตันด้วยสี brush ปัจจุบันออกมา นั่นคือ wedge ตันใต้ trend line ของกราฟที่ว่ากันดั้งเดิม ก่อน v3.539.41 PDFlibPas fill polyline ทั้งสองความกว้างด้วย brush และ record 32 บิตยังถูกปิดแบบชัดเจนอีก ตอนนี้ทั้งคู่จบด้วย stroke เท่านั้น และเส้นแบ่งของ GDI ยังอยู่ครบ: Polygon ปิดแล้ว fill ส่วน Polyline ไม่ทำสองอย่างนั้นเด็ดขาด
PolylineTo เริ่มจาก current position
EMR_POLYLINETO วาดจาก current position ลาดผ่านจุดทุกตัวใน record ค้างเปิดไว้ แล้ววาง current position ไว้ที่จุดสุดท้าย handler เดิมยังซ่อน special case ที่ปิด pen เมื่อสองจุดแรกใช้พิกัด y ร่วมกัน แล้วไม่มีอะไรเปิดคืนให้เลย record ทุกตัวถัดไปในไฟล์จึงหายเส้นขอบ สถานะ pen เป็นของ EMR_SELECTOBJECT กับ EMR_CREATEPEN handler ของ drawing record ไม่มีสิทธิ์แตะมัน special case นั้นถูกถอดออกใน v3.539.41 และ record แบบจุดเดียวก็ไม่อ่านล้ำเลยจุดของตัวเองอีก (แก้ใน v3.539.39)
จุดของ PolyPolyline เริ่มหลัง counts array
EMR_POLYPOLYLINE 32 บิตเก็บ count จำนวน nPolys ตัวตามด้วยจุด cptl จุด โดยจุดเริ่มที่ byte offset 32 + nPolys * 4 กับดักอยู่ที่ RTL: ยูนิต Windows ประกาศ TEMRPolyPolyline ให้ aPolyCounts กับ aptl เป็น array ช่องเดียว aptl[0] จึงเป็นจุดแรกเมื่อ nPolys เท่ากับ 1 เท่านั้น โค้ดที่ index aptl ตรง ๆ จะอ่านค่า count ไปเป็นพิกัดทุกครั้งที่เจอ record หลายเส้น handler เดิมของ PDFlibPas ยังคำนวณ bounds check บน layout ผิด ๆ นั้นด้วย record หลายเส้นที่ถูกต้องเลยโดนปฏิเสธ และ record เส้นเดียวกลับวาดไม่ออกอะไรเลย ตั้งแต่ v3.539.41 PDFlibPas หาตำแหน่ง point array จาก offset ที่คำนวณได้ แบบเดียวกับที่ handler PolyPolygon ของมันทำมาตลอด แล้ววาดแต่ละ polyline เป็น subpath เปิดของตัวเองพร้อม stroke ครั้งเดียวตอนจบ ส่วนฝาแฝด 16 บิตได้รับการดูแลแบบเดียวกันใน v3.539.43 ก่อนหน้านั้นมันวาดทีละ segment ซึ่งทำลาย line join และมองข้าม NULL_PEN ที่ถูกเลือกไว้
pen กับ brush ค่าเริ่มต้น และวงเล็บ path
กฎสถานะอีกสองข้อปิดท้ายชุดแก้ polyline ใน v3.539.43:
- device context ของ GDI ที่เพิ่งเปิดใหม่เลือก
BLACK_PENกับWHITE_BRUSHไว้แล้ว metafile ที่วาดโดยไม่แตะEMR_SELECTOBJECTเลยจึงยังวาดขอบดำออกมา converter เดิมเริ่มด้วย pen กับ fill ว่างเปล่าแล้วเขียนn(จบ path ไม่ลงสีอะไร) ให้ record พวกนี้ - ข้างในวงเล็บ
BeginPath/EndPathPolylineทั้งไม่ใช้และไม่อัปเดต current position มันจึงต้องเปิด subpath ใหม่ที่จุดแรกของตัวเอง ไม่ใช่ต่อเข้ากับรูปก่อนหน้า และห้ามลงสีอะไรจนกว่าวงเล็บจะถูก stroke หรือ fill
สร้างไฟล์ EMF ทดสอบด้วย TMetafileCanvas
วิธีไวสุดในการเช็ค converter กับกฎพวกนี้คืออัด call เสี่ยงทั้งสามตัวลง enhanced metafile ไฟล์เดียวด้วย TMetafileCanvas ภาพด้านล่างอัดเส้นโค้งด้วย brush แบบโปร่ง แล้วตั้งใจเลือก brush เหลืองทึบไว้ก่อนวาด polyline converter ที่ถูกต้องต้องมองข้าม brush ตัวนั้นตอนวาด polyline สีเหลืองแม้แต่จุดเดียวใน PDF ที่ได้ออกมาจึงเท่ากับพบบั๊ก PolyDraw ไม่มี wrapper บน TCanvas จึงต้องเรียกผ่าน Windows API ด้วย handle ของ canvas โดยใช้ type byte 3 กับ 5 เพื่อทดสอบ close flag
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 เป็น cubic segment สองท่อน
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; // จบการอัด record
end;
Mf.SaveToFile(FileName);
finally
Mf.Free;
end;
end;
พิกัดพวกนี้ลงตัวใน SmallInt GDI จึงมักเก็บเวอร์ชัน 16 บิตเป็นปกติ จะไปถึง handler ฝั่ง 32 บิตคุณต้องมี producer ที่เขียนมันออกมา หรือสร้าง record เองด้วยมือ ไฟล์ที่ปั้นเองก็มีกับดักของตัวเอง: VCL TMetafile.LoadFromStream ถือว่า stream เป็น EMF ก็ต่อเมื่อความยาวที่เหลือมากกว่า TEnhMetaHeader ขนาด 108 ไบต์อย่างเคร่งครัด EMF ที่เขียนมือแบบมินิมอลพร้อม header สั้น หรือไฟล์ว่างที่ยาวพอดี 108 ไบต์ จะโดนมองเป็น WMF แล้วปฏิเสธด้วยข้อความ "Metafile is not valid" สร้าง record ทดสอบเมื่อไรให้เขียน header เต็ม 108 ไบต์รวม field ส่วนขยายเสมอ
นำเข้า EMF เข้า PDF ด้วย PDFlibPas
PDFlibPas นำเข้า EMF ด้วย ImportEMFFromFile หรือ ImportEMFFromStream ซึ่งคืน image ID ไม่เป็นศูนย์เมื่อสำเร็จ และคืน 0 เมื่อล้ม GeneralOptions = 0 คง vector path ที่บทความนี้พูดถึง ส่วนค่า 1 จะ rasterize metafile เป็น bitmap แทน FontOptions = 1 เพิ่ม font ของ metafile เข้ามาเป็น TrueType แบบไม่ embed เวอร์ชัน stream จะกระโดน stream กลับไป position 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); // จุด origin มุมบนซ้ายสำหรับ DrawImage
PDF.SetMeasurementUnits(0); // หน่วยเป็น point
// FontOptions 1 = เพิ่ม font เป็น TrueType แบบไม่ embed
// 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 คือขนาด frame หน่วย point
PDF.DrawImage(36, 36, PDF.ImageWidth, PDF.ImageHeight);
// หน้ากระดาษเรียกใช้แค่ form ที่นำเข้ามา: 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;
การนำเข้า EMF แบบ vector กลายเป็น form XObject GetPageContentToString จึงคืนมาแค่ลำดับ save, transform, Do กับ restore operator m l c h และ S ที่ผลิตออกมาจาก record Poly* อยู่ใน stream ของ form XObject ซึ่งถูกบีบอัดไว้ จะตรวจพวกมันต้องคลายบีบอัดไฟล์ที่ save แล้วใน PDF object inspector แล้วอ่าน form stream: สำหรับไฟล์ทดสอบด้านบนคุณควรเห็น polyline จบด้วย S โดยไม่มี h นำหน้า มี h หนึ่งตัวตรง close flag แต่ละจุดในรูป PolyDraw และไม่มี f หรือ B ติดอยู่บน subpath เหล่านี้เลย DrawImage ยังขยาย EMF ที่นำเข้าแบบสมส่วนตามค่าที่น้อยกว่าระหว่าง Width กับ Height ภาพจึงคงสัดส่วนเดิมแม้กล่องที่ส่งเข้าไปจะไม่ตรงก็ตาม
ถ้าเป้าหมายเป็น Free Pascal ดูวิธีที่ EMF vector importer ของ PDFlibPas build ภายใต้ Free Pascal ส่วน semantics ของ record เหมือนเดิมไม่ว่า importer จะ compile ที่ไหน
EMF parser ควรเชื่อ point count จากไฟล์แค่ไหน
EMF parser ควรถือ point count ทุกตัวเป็น input ที่ไม่น่าเชื่อถือ แล้วเช็คกับขนาด record ก่อน copy จุดแม้แต่จุดเดียว EnumEnhMetaFile การันตีแค่ว่า nSize ของแต่ละ record อยู่ในไฟล์ มันไม่ได้เช็คว่า cptl สอดคล้องกับ nSize หรือเปล่า handler ที่ copy จุด cptl จุดด้วย Move จึงอ่าน record ข้าง ๆ ต่อไป หรืออ่านทะลุสุดท้ายของ metafile เมื่อ count โดนปลอมหรือพัง ตั้งแต่ v3.539.39 PDFlibPas เช็ค header คงที่บวก count คูณไบต์ต่อจุดเทียบกับ nSize ให้ PolyDraw, PolyBezier, PolyBezierTo, Polyline, PolylineTo และ Polygon ทั้งสองความกว้าง โดย PolyDraw บวกไบต์เพิ่มจุดละหนึ่งสำหรับ type byte สำหรับ record ตระกูล PolyPoly ค่า count ต่อรูปยังต้องรวมกันไม่เกินค่ารวมที่ประกาศ และรูปที่มีศูนย์จุดถูกข้ามไป
เช็คเดียวกันนี้สั้นพอจะก๊อปลง parser ของคุณเอง เวอร์ชันด้านล่างตรวจ EMR_POLYPOLYLINE 32 บิตแล้วคืน pointer ชี้ไปยัง point array จริงของมัน:
uses
Winapi.Windows;
// คืน nil เว้นแต่ record จะถือจุดครบตามที่มันประกาศจริง ๆ
// จุดเริ่มหลัง counts array: เข้าไป 32 + nPolys * 4 ไบต์ ไม่ใช่ที่
// aptl[0] ซึ่ง RTL ประกาศเป็น array ช่องเดียว
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; // count ถูกปลอมหรือถูกตัดขาด
Total := 0;
Count := @P^.aPolyCounts[0]; // เดินด้วย pointer: [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;
การทดสอบว่าจุดพอดีไหมรันก่อน counts array จึงถูกพิสูจน์แล้วว่าอยู่ใน record ก่อน loop จะเดินมัน ตัวเลขคำนวณด้วย Int64 เพราะ nPolys * 4 กับ cptl * 8 ที่คิดใน 32 บิตวนกลับได้ แล้วหลุดผ่านการเปรียบเทียบไปเฉย ๆ
สรุปด่วน: กฎ EMF Poly* สำหรับแปลง EMF เป็น PDF
EMR_POLYBEZIER: จุด 0 คือจุดเริ่ม จัดกลุ่มจากจุด 1 ทีละสาม แก้ให้ record 32 บิตแล้วใน v3.539.41EMR_POLYLINE/EMR_POLYPOLYLINE: รูปเปิด จบด้วยSห้ามh,fหรือBเพราะ fill ของ PDF ปิด subpath เปิด ๆEMR_POLYLINETO: เริ่มที่ current position ค้างเปิดไว้ อัปเดต current position และห้ามแตะสถานะ pen เด็ดขาดEMR_POLYPOLYLINE32 บิต: จุดเริ่มที่ byte32 + nPolys * 4ไม่ใช่ที่aptl[0]EMR_POLYDRAW: กลบPT_CLOSEFIGUREออกก่อน dispatch ปิดรูปหลัง segment จบสมบูรณ์ และเริ่มที่ current position เมื่อจุดแรกไม่ใช่PT_MOVETO- สถานะเริ่มต้นของ device context คือ
BLACK_PENคู่กับWHITE_BRUSHv3.539.43 ขึ้นไปให้เกียรติกฎนี้ - ข้างใน
BeginPath/EndPathpolyline แต่ละเส้นเปิด subpath ของตัวเอง และไม่มีอะไรถูกลงสีจนกว่าวงเล็บจะถูกใช้ - ตรวจ
cptl/cptsทุกตัวเทียบกับnSizeด้วยเลขคณิต 64 บิตก่อน copy จุด - EMF ทดสอบที่ปั้นเองต้องมี header เต็ม 108 ไบต์ ไม่อย่างนั้น
TMetafile.LoadFromStreamจะอ่านเป็น WMF
ถ้ารายงานของคุณผ่าน component ตัวอื่น semantics ของ record ก็ใช้เหมือนเดิม HotPDF EMF and WMF vector import เล่าว่า component ตัวนั้นแปลง brush แบบ gradient กับ hatch เป็น PDF pattern อย่างไร ส่วน vector graphics, shaders และ gradients ใน PDFlibPas เล่าการวาดรูปชุดเดียวกันนี้ตรง ๆ ด้วย library API แทนการผ่าน metafile
PDFlibPas v3.539.43 ขึ้นไปมีกฎทุกข้อข้างบนครบ รายละเอียดและดาวน์โหลดเวอร์ชันทดลองอยู่ที่หน้า product page ของ PDFlibPas Delphi PDF library