Technical Article

พิมพ์ PDF ที่โหลดใน Delphi ด้วย HotPDF และ TPrinter

HotPDF พิมพ์เอกสาร PDF ที่โหลดเข้ามาไปยังเครื่องพิมพ์จริง, บริบทอุปกรณ์ (device context) ใดๆ ของ Windows หรือคลิปบอร์ดผ่านการเรียกใช้งานสามคำสั่ง ได้แก่: PrintLoadedDocument ทำหน้าที่ควบคุม VCL TPrinter, RenderLoadedPageToDC วาดหน้ากระดาษลงใน HDC ใดๆ โดยตรง และ RenderLoadedPageToClipboard คัดลอกหน้ากระดาษในรูปแบบบิตแมปหรือเวกเตอร์เมตาไฟล์ HotPDF เป็นส่วนประกอบ VCL PDF ดั้งเดิมสำหรับ Delphi และ C++Builder และได้เพิ่มพาธการพิมพ์ระดับเฟิร์สคลาสนี้ในเวอร์ชัน v2.331.0 และปรับปรุงใน v2.348.0 เพื่อให้คุณไม่ต้องเขียนลูป TPrinter รอบตัวเรนเดอร์บิตแมปด้วยตนเองอีกต่อไป การเรียกทั้งสามคำสั่งนี้จะวาดเนื้อหาเวกเตอร์ของหน้ากระดาษโดยตรงไปยังอุปกรณ์เป้าหมาย ช่วยให้งานพิมพ์มีความคมชัดสูงสุดเท่าที่เครื่องพิมพ์จะสามารถเรนเดอร์ได้

เหตุใดจึงควรวาดหน้า PDF ลงในเครื่องพิมพ์โดยตรงแทนที่จะใช้บิตแมป?

หน้ากระดาษ PDF คือโปรแกรมของตัวดำเนินการวาดรูปเวกเตอร์ — เช่น พาธ, ข้อความ และรูปภาพที่ประมวลผลเทียบกับแบบจำลองกราฟิก ISO 32000-1 — ไม่ใช่ตารางพิกเซล วิธีปกติทั่วไปในการพิมพ์คือการแปลงหน้ากระดาษให้เป็นแรสเตอร์บิตแมปแล้วส่งภาพบิตแมปนั้นไปที่แคนวาส (canvas) ของเครื่องพิมพ์ วิธีนี้ใช้งานได้จริงแต่จะเป็นการสูญเสียประสิทธิภาพที่แท้จริงของเครื่องพิมพ์ เครื่องพิมพ์เลเซอร์สมัยใหม่สามารถประมวลผลความละเอียดได้ถึง 600 หรือ 1200 DPI ในขณะที่รูปภาพบิตแมปที่แปลงความละเอียดมาเพียง 150 หรือ 300 DPI เมื่อนำมาขยายขนาดให้พอดีกระดาษจะทำให้ภาพเบลอ ตัวหนังสือหยัก และเห็นรอยแตกได้ชัดเจน เมธอด RenderLoadedPageToDC จึงเลือกใช้วิธีที่ดีกว่าคือ: การนำเอาสตรีมเนื้อหาของหน้ากระดาษมารันใหม่เป็นคำสั่ง GDI ส่งไปยังบริบทอุปกรณ์ปลายทางโดยตรง เพื่อให้ข้อความเวกเตอร์และลายเส้นส่งไปถึงเครื่องพิมพ์ในรูปแบบเวกเตอร์และผ่านการแปลงแรสเตอร์เพียงครั้งเดียวโดยเอนจินของเครื่องพิมพ์ที่ความละเอียดดั้งเดิมของตัวเครื่องพิมพ์เอง

จะพิมพ์ PDF ที่โหลดเข้ามาไปยังเครื่องพิมพ์จริงใน Delphi ได้อย่างไร?

HotPDF พิมพ์เอกสารที่โหลดเข้ามาด้วย PrintLoadedDocument(Printer, Options) ซึ่งจะรับออบเจกต์ VCL TPrinter ที่กำหนดให้ และโครงสร้างข้อมูล THPDFPrintOptions ทำการพิมพ์หน้าเอกสารที่เลือก และส่งคืนจำนวนหน้าที่ส่งออกไป ก่อนอื่นให้กำหนด Printer.PrinterIndex เพื่อเลือกอุปกรณ์ จากนั้นกรอกข้อมูลตัวเลือกในโครงสร้างข้อมูล — ฟังก์ชันคลาส THPDFPrintOptions.Default จะให้ค่าเริ่มต้นที่เหมาะสมแก่คุณ — แล้วเรียกใช้หนึ่งครั้ง ไลบรารีจะเปิดงานพิมพ์ด้วย Printer.BeginDoc วนลูปตามช่วงหน้ากระดาษโดยแทรกคำสั่ง Printer.NewPage ระหว่างหน้า และปิดงานพิมพ์ด้วย Printer.EndDoc ทำให้คุณไม่ต้องจัดการวงจรอายุของงานพิมพ์ด้วยตัวเองเลย

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 selects the default printer
      Opts := THPDFPrintOptions.Default;  // all pages, 300 DPI, FitPage, colour
      Opts.PageRange := '1,3-5';          // one-based; empty prints every page
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

สตริง PageRange จะเริ่มนับจาก 1 และอ่านค่าได้เช่นเดียวกับที่กำหนดในกล่องโต้ตอบการพิมพ์: ตัวอย่างเช่น '1,3-5' จะพิมพ์หน้า 1 และหน้า 3 ถึง 5 ส่วนสตริงว่างจะหมายถึงการพิมพ์เอกสารทั้งหมด Copies จะชี้ไปยัง Printer.Copies โดยตรง อย่างไรก็ตาม ควรพิจารณาเรื่อง Collate (จัดชุด) ตามจริง: เนื่องจาก VCL แสดงคุณสมบัติการจัดชุดไม่สอดคล้องกันในแต่ละเวอร์ชันของ Delphi ตัว HotPDF จึงทำหน้าที่เพียงกำหนดจำนวนสำเนาและปล่อยให้การจัดชุดเป็นหน้าที่ของไดรเวอร์เครื่องพิมพ์เริ่มต้น ดังนั้นหากคุณต้องการความถูกต้องแน่นอนของการจัดชุดเอกสาร โปรดเลือกตั้งค่าที่ตัวไดรเวอร์ผ่าน TPrinterSetupDialog แทนที่จะพึ่งพาแฟล็กดังกล่าว

DPI และ FitPage กำหนดขนาดการพิมพ์อย่างไร?

การทำงานร่วมกันระหว่าง FitPage และ DPI เป็นสิ่งที่คุณควรทำความเข้าใจก่อนเริ่มงานจริง เมื่อตั้งค่า FitPage เป็น True — ซึ่งเป็นค่าเริ่มต้น — HotPDF จะอ่านค่า MediaBox ของแต่ละหน้า ปรับขนาดให้พอดีกับ Printer.PageWidth และ Printer.PageHeight โดยรักษาสัดส่วนภาพเอาไว้ และเรนเดอร์ในขนาดที่ทำให้หน้ากระดาษแสดงผลเต็มกระดาษพิมพ์ ในโหมดนี้ ค่า Options.DPI จะถูกเขียนทับด้วยขนาดที่ปรับให้พอดีเนื่องจากเอาต์พุตจะต้องสอดคล้องกับมิติกระดาษจริงของเครื่องพิมพ์ แต่หากกำหนด FitPage เป็น False ตัว HotPDF จะเรนเดอร์แต่ละหน้าในอัตราส่วน 1:1 ที่ความละเอียด Options.DPI พอดี โดยจับคู่ 72 หน่วย PDF เข้ากับพิกเซลอุปกรณ์ DPI ซึ่งเหมาะสำหรับกรณีที่หน้ากระดาษต้นทางและกระดาษพิมพ์มีขนาดเท่ากันอยู่แล้ว และคุณต้องการกำหนดความละเอียดที่แน่นอน เลือกใช้ FitPage เมื่อขนาดหน้าต้นทางและถาดกระดาษพิมพ์ต่างกัน และเลือกใช้ 1:1 เมื่อขนาดตรงกันและคุณต้องการควบคุมขนาดการพิมพ์ที่แม่นยำ

ตัวเลือกสองตัวที่เพิ่มเข้ามาในเวอร์ชัน v2.348.0 จะช่วยแก้ปัญหาเครื่องพิมพ์ที่พิมพ์ออกมาเกือบจะถูกต้อง เครื่องพิมพ์จริงทุกเครื่องจะมีระยะขอบแข็ง (hard margin) — ซึ่งเป็นขอบเขตพิมพ์ไม่ได้ที่หัวพิมพ์ไม่สามารถเข้าถึง — และตามค่าเริ่มต้น จุดเริ่มต้นหน้ากระดาษจะอยู่ภายในระยะขอบนั้น ทำให้หน้ากระดาษแบบพิมพ์เต็มขอบ (full-bleed) สูญเสียขอบไปสองด้าน หากกำหนด IgnoreHardMargin เป็น True ตัว HotPDF จะค้นหาค่า PHYSICALOFFSETX และ PHYSICALOFFSETY ของเครื่องพิมพ์ จากนั้นจะเลื่อนจุดเริ่มต้นอุปกรณ์เพื่อล้างค่าออฟเซ็ตนั้นออกไป ช่วยให้จุดเริ่มต้นหน้ากระดาษตรงกับมุมกระดาษจริงอย่างถูกต้อง ส่วนตัวเลือกที่สองคือ AutoRotate จะเปิดทำงานตามค่าเริ่มต้น: โดยจะอ่าน MediaBox ของแต่ละหน้า และเมื่อหน้ากระดาษมีความกว้างมากกว่าความสูง มันจะสลับโหมดเครื่องพิมพ์เป็นแนวนอน (landscape) เพื่อให้หน้ากระดาษกว้างได้รับการพิมพ์โดยไม่ถูกตัดขอบ แทนที่จะถูกตัดให้เหลือเท่าแนวตั้ง AutoRotate จะหยุดทำงานทันทีที่คุณกำหนด Orientation เองอย่างชัดเจน ดังนั้นการตั้งค่าการจัดวางกระดาษโดยเจตนาจะมีผลเหนือกว่าเสมอ

จะเรนเดอร์หน้า PDF ลงในบริบทอุปกรณ์ใดๆ ได้อย่างไร?

RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) คือฟังก์ชันระดับล่างที่อยู่ภายใต้การทำงานของลูปการพิมพ์ และมีประโยชน์ในการใช้งานแยกต่างหาก PrintLoadedDocument จะเรียกใช้งานฟังก์ชันนี้หนึ่งครั้งต่อหนึ่งหน้าเทียบกับแคนวาสของเครื่องพิมพ์ แต่เป้าหมายสามารถเปลี่ยนเป็น HDC ใดๆ ของ Windows ได้ เช่น บริบทอุปกรณ์หน่วยความจำนอกจอ (off-screen memory DC), แคนวาสของส่วนควบคุมในโปรแกรมดูภาพแบบกำหนดเอง หรือหน้าต่างแสดงตัวอย่างก่อนพิมพ์ โดย PageIndex จะเริ่มจาก 0 ส่วน W และ H คือหน่วยอุปกรณ์ของพื้นที่เป้าหมาย ตัวเรนเดอร์จะไล่ดูข้อมูล /Contents ของหน้ากระดาษและส่งคำสั่ง GDI ไปที่ DC ของคุณ พฤติกรรมประการหนึ่งที่คุณควรทราบคือ: เมธอดนี้จะไม่มีการวาดพื้นหลังสีพื้น ผู้เรียกใช้งานเป็นผู้ครอบครองพื้นที่วาด ดังนั้นหากคุณต้องการให้มีกระดาษสีขาวอยู่เบื้องหลังหน้ากระดาษ คุณจะต้องถมสี DC ด้วยตนเองก่อนเริ่มเรนเดอร์ ซึ่งวิธีนี้ช่วยให้คุณสามารถนำหน้ากระดาษไปวางซ้อนทับบนแคนวาสที่มีอยู่แล้วได้ตามต้องการ

// Draw page 1 into an off-screen 24-bit surface at 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 in at 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // caller paints the background
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // Surface now holds the composited page, ready to blit or save
  finally
    Surface.Free;
  end;
end;

เมื่อคุณต้องการเพียงออบเจกต์ TBitmap ที่พร้อมใช้งาน — เช่น ภาพตัวอย่างขนาดเล็ก (thumbnail), การส่งออกรูปภาพ หรือภาพตัวอย่างก่อนพิมพ์ที่คุณต้องการแคชไว้ — ให้เลือกใช้เมธอด RenderLoadedPageToBitmap แทน ซึ่งอธิบายรายละเอียดไว้ในบทความคู่กันเรื่อง rendering PDF pages to a bitmap ความแตกต่างคือใครเป็นผู้ครอบครองพื้นที่วาด: การเรียกใช้บิตแมปจะจัดสรรหน่วยความจำและส่งคืนพิกเซลบัฟเฟอร์กลับมาให้ ในขณะที่ RenderLoadedPageToDC จะวาดภาพลงในบริบทอุปกรณ์ที่คุณมีอยู่แล้ว ซึ่งเป็นสิ่งที่แคนวาสเครื่องพิมพ์หรือตัวจัดการการวาดส่งต่อมาให้คุณ ทั้งสองแบบจะใช้ล่ามแปลสตรีมเนื้อหาและเอนจินประมวลผลตัวอักษรฝังตัวตัวเดียวกัน ดังนั้นข้อความที่แสดงออกมาจะมีลักษณะโครงร่างเส้นที่ถูกต้องแม่นยำเหมือนกันทั้งสองวิธี

จะวางหน้า PDF ลงใน Word หรือโปรแกรมแก้ไขรูปภาพได้อย่างไร?

RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) จะส่งหน้ากระดาษหนึ่งหน้าไปยังคลิปบอร์ดของ Windows เพื่อเตรียมพร้อมสำหรับการวางลงใน Word, PowerPoint, อีเมล หรือโปรแกรมแก้ไขรูปภาพ แฟล็ก AsMetafile จะเป็นตัวเลือกรูปแบบ ซึ่งมีความสำคัญมากกว่าที่เห็นภายนอก หากส่งค่า False ตัว HotPDF จะส่งบิตแมปอุปกรณ์ขนาด 24 บิต (CF_BITMAP) ที่เรนเดอร์ตามความละเอียด DPI ของคุณ — ซึ่งเป็นตารางพิกเซลคงที่ซึ่งสามารถวางได้ทุกที่แต่ภาพจะแตกเมื่อผู้รับนำไปขยายขนาด หากส่งค่า True มันจะส่งข้อมูลเป็น Enhanced Metafile (CF_ENHMETAFILE) ซึ่งข้อความและภาพเวกเตอร์บนหน้ากระดาษจะถูกส่งเป็นชุดคำสั่งการวาดของ GDI ทำให้รูปภาพที่วางยังคงความชัดเจนในทุกระดับการซูมและย่อขยายขนาดได้ตามต้องการในเอกสารปลายทาง

// Copy page 2 as a scalable vector metafile for pasting into Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copy the same page as a fixed 300-DPI bitmap instead
Pdf.RenderLoadedPageToClipboard(1, 300, False);

กฎเกณฑ์พิจารณาง่ายๆ คือ: หากผู้ใช้ต้องการนำหน้ากระดาษไปวางลงในเอกสารและอาจปรับขนาดภายหลัง — เช่น แผนภูมิในรายงาน หรือข้อกำหนดที่แทรกลงในร่างสัญญา — ให้เลือกใช้รูปแบบเมตาไฟล์ เนื่องจากข้อความเวกเตอร์จะพิมพ์และซูมได้อย่างคมชัด แต่หากโปรแกรมปลายทางรองรับเฉพาะข้อมูลภาพแรสเตอร์ หรือคุณต้องการบันทึกภาพนิ่งที่มีความละเอียดเฉพาะเจาะจง ให้เลือกใช้บิตแมป ส่วนการดึงคำศัพท์ออกจากหน้าเอกสารแทนการบันทึกภาพจะเป็นงานอีกประเภทหนึ่งซึ่งจัดการโดย the text extraction API ซึ่งจะส่งคืนค่าตัวอักษรและตำแหน่งแทนที่จะเป็นรูปภาพที่เรนเดอร์แล้ว

ข้อจำกัดของพาธการพิมพ์

ฟังก์ชันการทำงานทั้งหมดในหน้านี้รองรับเฉพาะระบบปฏิบัติการ Windows เท่านั้น และนี่คือข้อจำกัดที่ควรระบุอย่างชัดเจน PrintLoadedDocument, RenderLoadedPageToDC และ RenderLoadedPageToClipboard ถูกสร้างขึ้นบนโครงสร้างของ TPrinter, โมเดลบริบทอุปกรณ์ GDI, คลิปบอร์ดของ Windows และ Enhanced Metafiles — ซึ่งล้วนเป็นโครงสร้างของ Win32 และ Win64 สำหรับเป้าหมายระบบอื่นนั้น ตัว HotPDF จะยังคงสามารถสร้าง แก้ไข และเปิดอ่าน PDF ได้ตามปกติ แต่จะไม่สามารถใช้งานส่วนการพิมพ์และคลิปบอร์ดที่ทำงานร่วมกับ GDI ได้ นอกจากนี้ตัวเรนเดอร์จะมุ่งเน้นไปที่ส่วนประกอบเอกสารทั่วไป เช่น ข้อความ, พาธ และรูปภาพ ดังนั้นหน้าออกแบบที่มีการไล่สีหนาแน่นหรือการพิมพ์ที่ต้องใช้ระบบสี ICC-managed จะแสดงผลเป็นภาพตัวอย่างที่ใกล้เคียงแทนที่จะเป็นสีที่ถูกต้องแม่นยำตามมาตรฐานทางสี ซึ่งเป็นข้อจำกัดทำนองเดียวกับตัวเรนเดอร์บิตแมป

สำหรับการพิมพ์แบบกลุ่มข้ามไฟล์หลายไฟล์ ให้ทำการโหลดและพิมพ์แต่ละเอกสารตามลำดับและติดตามการใช้งานหน่วยความจำ: PrintLoadedDocument จะเรนเดอร์ทีละหน้า ทว่าโครงสร้างออบเจกต์ของเอกสารที่โหลดเข้ามาจะคงอยู่ในหน่วยความจำตลอดช่วงการทำงาน เมื่อคุณต้องการพิมพ์ไฟล์ขนาดใหญ่จำนวนหลายร้อยไฟล์ การสตรีมมิ่งผ่าน Direct File API for large-PDF workflows จะทำงานร่วมกับพาธการพิมพ์ได้เป็นอย่างดี — โดยแยก, รวม หรือเลือกเฉพาะหน้ากระดาษที่คุณต้องการบนดิสก์ก่อน จากนั้นจึงโหลดเฉพาะผลลัพธ์ขนาดเล็กเข้ามาพิมพ์ วิธีนี้คือลักษณะการส่งมอบงานของ PrintLoadedDocument, RenderLoadedPageToDC และ RenderLoadedPageToClipboard ซึ่งเป็นส่วนหนึ่งของ HotPDF Component สำหรับ Delphi และ C++Builder — ซึ่งเป็นไลบรารี VCL ดั้งเดิมที่ไม่มีการพึ่งพาไฟล์ DLL ภายนอก รองรับทั้งการสร้าง, แก้ไข, ดึงข้อความ, เรนเดอร์ และขณะนี้สามารถพิมพ์โดยตรงไปที่ TPrinter, บริบทอุปกรณ์ใดๆ หรือคลิปบอร์ดได้เรียบร้อยแล้ว