บทความเทคนิค

PDFlibPas นำเข้า EMF: กฎของ PolyDraw, Polyline และ Bezier

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_POLYLINETOcurrent positionไม่ (ใช้แค่ pen)ใช้และอัปเดต
EMR_POLYPOLYLINEจุดแรกของแต่ละ polylineไม่ (ใช้แค่ pen)ไม่ใช้ ไม่อัปเดต
EMR_POLYDRAWPT_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 หนึ่งตัวต่อกลุ่มสามจุดที่ครบหลังจากนั้น

แผนภาพ EMR_POLYBEZIER เจ็ดจุดของ PDFlibPas ที่จุดศูนย์เปิด path ด้วย m และจุดหนึ่งถึงสามกับสี่ถึงหกจัดเป็น segment cubic c ท่อนละกลุ่ม เทียบ handler 32 บิตที่แก้แล้วตั้งแต่ v3.539.41 กับการจัดกลุ่มแบบเก่าที่กินจุดเริ่มไปเป็น control point
จุด 0 คือจุดเริ่ม และมีแค่กลุ่มสามจุดที่ครบหลังจากนั้นเท่านั้นที่กลายเป็น cubic segment PolyBezier เจ็ดจุดจึง render ออกมาเป็น m บวก operator 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_POLYDRAW16 16 บิตเคยรีสตาร์ท path ทั้งเส้นใหม่ record ที่ถือรูปสามรูปเลยเหลือแค่รูปสุดท้าย ตอนนี้ move แรกเริ่ม path และ move ถัดไปเปิด subpath ใหม่
  • record PolyDraw ที่ไม่ได้เริ่มด้วย PT_MOVETO จะเริ่มที่ current position ตามที่นิยาม record บอก แทนที่จะเขียน operator l หรือ c ออกมาโดยไม่มี m นำหน้า
กายวิภาค type byte ของ EMR_POLYDRAW ใน PDFlibPas ที่ PT_CLOSEFIGURE เป็นบิต flag ลำดับศูนย์หรือรวมเข้ากับ PT_LINETO หรือ PT_BEZIERTO type byte 3 กับ 5 ที่ถูกต้องจึงต้องกลบด้วย and not PT_CLOSEFIGURE ก่อน dispatch case statement แบบเก่าข้าม byte ทั้งคู่ทิ้ง และรูปที่ปิดขาดด้านสุดท้ายไปหนึ่งด้าน
กลบ close flag ออกก่อน dispatch แล้วปล่อยคำสั่งปิดหลังเส้นหรือกลุ่มสามจุด Bezier จบสมบูรณ์เท่านั้น ไม่อย่างนั้น PolyDraw จะทิ้งจุดไปเงียบ ๆ

ทำไม 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 ไม่ทำสองอย่างนั้นเด็ดขาด

เทียบ polyline V เปิด ๆ ที่ export จาก EMR_POLYLINE ใน PDFlibPas converter ที่ถูกต้องจบ path ด้วย stroke operator S และมองข้าม brush ที่เลือกไว้ ขณะที่การปล่อย f หรือ B ปิด subpath เปิด ๆ โดยปริยายตาม ISO 32000-1 8.5.3 แล้ววาด wedge ตัน บั๊กกราฟสุดคลาสสิก
fill operator ปิด subpath เปิด ๆ ก่อนลงสีเสมอ polyline จึงต้องจบด้วย S โดยไม่มี h, f หรือ B แปะอยู่บน subpath

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 / EndPath Polyline ทั้งไม่ใช้และไม่อัปเดต 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.41
  • EMR_POLYLINE / EMR_POLYPOLYLINE: รูปเปิด จบด้วย S ห้าม h, f หรือ B เพราะ fill ของ PDF ปิด subpath เปิด ๆ
  • EMR_POLYLINETO: เริ่มที่ current position ค้างเปิดไว้ อัปเดต current position และห้ามแตะสถานะ pen เด็ดขาด
  • EMR_POLYPOLYLINE 32 บิต: จุดเริ่มที่ byte 32 + nPolys * 4 ไม่ใช่ที่ aptl[0]
  • EMR_POLYDRAW: กลบ PT_CLOSEFIGURE ออกก่อน dispatch ปิดรูปหลัง segment จบสมบูรณ์ และเริ่มที่ current position เมื่อจุดแรกไม่ใช่ PT_MOVETO
  • สถานะเริ่มต้นของ device context คือ BLACK_PEN คู่กับ WHITE_BRUSH v3.539.43 ขึ้นไปให้เกียรติกฎนี้
  • ข้างใน BeginPath / EndPath polyline แต่ละเส้นเปิด 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