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

ไฮเปอร์ลิงก์ HotPDF Delphi: เคล็ดลับคำอธิบายประกอบ PrintHyperlink

ไฮเปอร์ลิงก์ใน PDF คือคำอธิบายประกอบแบบ URI (URI annotations): ซึ่งเป็นพื้นที่สี่เหลี่ยมบนหน้ากระดาษที่เมื่อคลิก จะสั่งให้โปรแกรมอ่านเปิด URL ที่กำหนด คำอธิบายประกอบและข้อความที่อยู่ด้านล่างนั้นเป็นออบเจกต์ที่แยกจากกันอย่างสิ้นเชิง PrintHyperlink ของ HotPDF รวมทั้งสองอย่างเข้าไว้ด้วยกันในการเรียกเพียงครั้งเดียว โดยวาดข้อความและคำนวณพื้นที่สี่เหลี่ยมของคำอธิบายประกอบจากขนาดตัวอักษรที่เรนเดอร์ ความสะดวกนี้ซ่อนรายละเอียดที่คุ้มค่าแก่การทำความเข้าใจก่อนที่คุณจะเขียนโค้ดสำหรับระบบจริง

PrintHyperlink ทำงานอย่างไร

PrintHyperlink อยู่ใน THPDFPage และรับอาร์กิวเมนต์สี่ตัว: พิกัด X และ Y (ในหน่วยพอยต์ จุดกำเนิดซ้ายล่าง Y เพิ่มขึ้นไปด้านบน) สตริงป้ายกำกับที่จะวาด และ URL เป้าหมาย ภายในระบบจะเรียก TextOut ด้วยสีไฮเปอร์ลิงก์ปัจจุบัน จากนั้นจะคำนวณพื้นที่สี่เหลี่ยมของคำอธิบายประกอบจาก TextWidth และ TextHeight ตามขนาดฟอนต์ปัจจุบันในทันที ซึ่งหมายความว่าต้องตั้งค่าฟอนต์และขนาดก่อนการเรียกใช้ และต้องไม่มีการเปลี่ยนแปลงระหว่างการวาดป้ายกำกับและการวางคำอธิบายประกอบ เนื่องจากทั้งสองอย่างนี้ถูกจัดการพร้อมกันในการเรียกครั้งเดียว

สีเริ่มต้นคือ clBlue การเรียก SetRGBHyperlinkColor จะเปลี่ยนสีสำหรับการเรียกครั้งต่อไปเท่านั้น จะไม่มีผลย้อนหลังกับคำอธิบายประกอบที่เขียนไปแล้ว หากคุณต้องการสีที่แตกต่างกันสำหรับกลุ่มลิงก์ต่าง ๆ ในหน้าเดียวกัน ให้เรียก SetRGBHyperlinkColor ก่อนแต่ละกลุ่มแล้วตั้งค่ากลับหลังจากนั้น

นี่คือเอกสารขนาดเล็กที่สุดที่เขียนลิงก์สามรายการด้วยสองสีที่แตกต่างกัน:

procedure CreateLinkedReport(const FileName: string);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.BeginDoc;

    Pdf.CurrentPage.SetFont('Arial', [], 11);

    // Default blue for informational links
    Pdf.CurrentPage.TextOut(50, 750, 0, 'Reference links:');
    Pdf.CurrentPage.PrintHyperlink(50, 720, 'Product page', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
    Pdf.CurrentPage.PrintHyperlink(50, 695, 'Online manual', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');

    // Red for the action link
    Pdf.CurrentPage.SetRGBHyperlinkColor(clRed);
    Pdf.CurrentPage.PrintHyperlink(50, 660, 'Purchase license', 'https://www.loslab.com/en-us/buy-hotpdf-fastspring.html');
    Pdf.CurrentPage.SetRGBHyperlinkColor(clBlue);  // restore default

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

กับดักพิกัด

HotPDF ใช้จุดกำเนิดซ้ายล่างโดยที่ Y เพิ่มขึ้นไปด้านบน ในหน่วยพอยต์ (1/72 นิ้ว) หน้า A4 มีขนาด 595 x 842 pt; หน้า US Letter มีขนาด 612 x 792 pt ค่า Y=750 จะอยู่ใกล้ขอบบนของหน้า A4 และ Y=50 จะอยู่ใกล้ขอบล่าง ผู้ที่คุ้นเคยกับกราฟิกบนหน้าจอหรือ HTML มักจะคิดตรงกันข้ามและเผลอวางลิงก์บรรทัดแรกหลุดออกจากพื้นที่แสดงผล

พื้นที่สี่เหลี่ยมคำอธิบายประกอบที่ PrintHyperlink คำนวณจะใช้ระบบพิกัดเดียวกัน หากคุณหมุนหน้ากระดาษ ย่อขยาย หรือเปลี่ยนขนาดหน้ากระดาษในภายหลังโดยไม่คำนวณค่า X/Y ใหม่ ข้อความที่มองเห็นและพื้นที่สี่เหลี่ยมที่คลิกได้จะเคลื่อนออกจากกัน ลิงก์ยังคง "ทำงาน" ได้ในแง่ที่ว่าการคลิกบริเวณใกล้เคียงกับข้อความจะกระตุ้น URL แต่จุดที่คลิกได้จริงจะไม่ตรงกับที่ผู้อ่านเห็นอีกต่อไป ควรทดสอบกับขนาดหน้ากระดาษและระดับการซูมจริงที่คุณตั้งใจจะเผยแพร่ ไม่ใช่แค่บนเครื่องพัฒนาที่ซูม 100% เท่านั้น

กรณีหนึ่งที่การเคลื่อนออกจากกันจะเกิดขึ้นแน่นอน: หากคุณเรียก PrintHyperlink ด้วยพิกัดที่เหมาะสมกับหน้า A4 แล้วเปลี่ยนไปใช้หน้าฟอร์แมตแคบแบบกำหนดเองโดยไม่ปรับพิกัด X/Y คำอธิบายประกอบอาจหลุดออกนอกหน้ากระดาษไปเลย ออบเจกต์คำอธิบายประกอบจะยังคงถูกเขียนลงใน PDF เพียงแต่โปรแกรมอ่านส่วนใหญ่จะตัดทิ้งอย่างเงียบ ๆ ทำให้ลิงก์หายไปโดยไม่มีข้อผิดพลาดใด ๆ แจ้งเตือน

ข้อความป้ายกำกับกับ URL เป้าหมาย

อาร์กิวเมนต์ Text และ Link เป็นอิสระจากกัน คุณสามารถวาดคำว่า "Download invoice PDF" ในขณะที่เป้าหมายเป็น URL HTTPS แบบเต็มรูปแบบที่มีพารามิเตอร์คิวรี การแยกกันนี้เป็นความตั้งใจ ป้ายกำกับที่มองเห็นควรเป็นภาษาที่คนอ่านเข้าใจได้ และ URL สามารถยาวหรือสร้างขึ้นแบบไดนามิกได้

สิ่งที่จะสร้างปัญหาคือเมื่อป้ายกำกับเป็น URL ดิบเสียเอง โดยเฉพาะเมื่อมันยาวมาก หาก URL ม้วนตัวเป็นสองบรรทัดในแง่ของการแสดงผล แต่พื้นที่สี่เหลี่ยมคำอธิบายประกอบถูกคำนวณสำหรับสตริงบรรทัดเดียว จะมีเพียงบรรทัดแรกเท่านั้นที่คลิกได้ PrintHyperlink ไม่รองรับการไหลของข้อความหลายบรรทัด ให้รักษาป้ายกำกับให้สั้นพอที่จะอยู่ในบรรทัดเดียวที่ขนาดฟอนต์และความกว้างหน้ากระดาษปัจจุบัน หรือใช้ป้ายกำกับสั้น ๆ ที่อธิบายความหมายโดยมี URL เต็มเป็นเป้าหมาย

สำหรับเอกสารที่จะถูกจัดเก็บหรือแจกจ่ายโดยไม่มีการเชื่อมต่ออินเทอร์เน็ตที่เปิดใช้งานอยู่ ให้พิจารณาด้วยว่า URL เองควรปรากฏในรูปแบบการพิมพ์ที่ใดที่หนึ่งในเนื้อหาเอกสารด้วย ไม่ใช่แค่เป็นข้อมูลเมตาของคำอธิบายประกอบ ผู้อ่านที่พิมพ์ PDF ออกมาบนกระดาษจะไม่ได้รับประโยชน์ใด ๆ จากคำอธิบายประกอบ URI เลย

การแก้ข้อจำกัดของข้อความหลายบรรทัด

PrintHyperlink เหมาะกับป้ายกำกับบรรทัดเดียว หากข้อความต้องห่อบรรทัด ให้ใช้ TextOut วาดแต่ละบรรทัดเอง แล้วสร้างพื้นที่คลิกที่ครอบคลุมทุกบรรทัดด้วย AddURILink การแยกข้อความที่มองเห็นออกจากคำอธิบายประกอบทำให้การจัดวางและพื้นที่กดไม่ขึ้นต่อการคำนวณความกว้างของสตริงเพียงบรรทัดเดียว

AddURILink: พื้นที่คลิกเหนือทุกสิ่งที่วาด

AddURILink ใช้สร้างคำอธิบายประกอบ URI จากสี่เหลี่ยมพิกัดโดยไม่สนใจว่าด้านล่างเป็นข้อความ รูปภาพ หรือรูปทรงเวกเตอร์ใด จึงเหมาะกับปุ่มที่วาดเองและกับป้ายกำกับหลายบรรทัด พื้นที่นี้เป็นข้อมูลโต้ตอบแยกจาก content stream และจะไม่เปลี่ยนสิ่งที่ผู้อ่านเห็นบนหน้า

การนำทางภายในด้วย AddGoToLink

สำหรับการเชื่อมโยงไปยังหน้าอื่นในเอกสารเดียวกัน ให้ใช้ AddGoToLink แทน URI ภายนอก เมธอดนี้ผูกสี่เหลี่ยมกับปลายทางภายใน จึงไม่ต้องพึ่งเครือข่ายและไม่เปิดคำเตือนความปลอดภัยแบบลิงก์ไปเว็บ แต่ยังต้องคำนวณพิกัดหน้าและดัชนีปลายทางให้ตรงกับเอกสารที่สร้างจริง

ตัวอย่างการสร้างเอกสารฉบับสมบูรณ์

รูปแบบด้านล่างแสดงสถานการณ์ที่สมจริงยิ่งขึ้น: การสร้างรายงานขนาดสั้นที่มีส่วนหัว ข้อความเนื้อหา และลิงก์ที่ส่วนท้ายกระดาษ ทั้งหมดทำจากโค้ดแทนที่จะมาจากฟอร์มที่มีฟิลด์ TEdit:

procedure GenerateProductSheet(
  const FileName, ProductName, ProductURL, SupportURL: string);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Compression := cmFlateDecode;
    Pdf.BeginDoc;

    // Header
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));

    // Body paragraph placeholder
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');

    // Footer links
    Pdf.CurrentPage.SetFont('Arial', [], 10);
    Pdf.CurrentPage.TextOut(50, 80, 0, 'Links:');
    Pdf.CurrentPage.PrintHyperlink(50, 60, 'Product page', ProductURL);
    Pdf.CurrentPage.PrintHyperlink(200, 60, 'Support', SupportURL);

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

สังเกตว่ามีการเรียก SetFont ก่อนการเรียกข้อความแต่ละกลุ่ม ฟอนต์จะไม่ถูกจดจำเมื่อเรียก AddPage และถ้าคุณลืมตั้งค่าฟอนต์ก่อนเรียก PrintHyperlink บนหน้าใหม่ พื้นที่สี่เหลี่ยมของคำอธิบายประกอบจะถูกคำนวณตามขนาดค่าเริ่มต้นของหน้ากระดาษ ซึ่งอาจแตกต่างจากที่คุณคาดไว้

ความแตกต่างของการจัดการคำอธิบายประกอบในโปรแกรมอ่านต่าง ๆ

คำอธิบายประกอบ URI ของ PDF ถูกกำหนดไว้ใน ISO 32000-1 §12.6.4.7 และโปรแกรมอ่านที่ได้มาตรฐานทุกตัวควรปฏิบัติตาม ในความเป็นจริง มีพฤติกรรมบางอย่างที่แตกต่างกันไปตามโปรแกรมอ่าน Adobe Acrobat จะแสดงคำเตือนความปลอดภัยเมื่อคลิกครั้งแรกสำหรับ URL ที่ไม่อยู่ในรายการโดเมนที่เชื่อถือได้ ในขณะที่เบราว์เซอร์และโปรแกรมอ่านขนาดเล็กส่วนใหญ่จะไม่แสดง โปรแกรมอ่าน PDF ระดับองค์กรในสภาพแวดล้อมที่ถูกจำกัดอย่างเข้มงวดจะปิดใช้งานคำอธิบายประกอบ URI ทั้งหมดตามนโยบาย ดังนั้นการคลิกจึงไม่เกิดผลอะไร และไม่มีข้อผิดพลาดแสดงให้เห็น แอป PDF บนมือถือมีความแตกต่างกันตรงที่อาจเปิดลิงก์ใน web view ภายในแอป หรือส่งต่อไปยังเบราว์เซอร์ของระบบ

สิ่งเหล่านี้ไม่ใช่ข้อบกพร่องที่คุณสามารถแก้ไขได้จากฝั่งการสร้างไฟล์ แต่เป็นการตัดสินใจด้านนโยบายของโปรแกรมอ่าน สิ่งที่คุณทำได้คือเขียนป้ายกำกับลิงก์ที่ทำให้ URL ปรากฏในเนื้อหาเอกสารด้วย เพื่อให้ผู้อ่านในสภาพแวดล้อมที่ถูกจำกัดยังสามารถคัดลอกที่อยู่ได้ด้วยตนเอง คำอธิบายประกอบเป็นเพียงความสะดวก ข้อความเป็นตัวเลือกสำรอง

รายละเอียดอีกประการที่ควรทราบ: คำอธิบายประกอบ URI ของ PDF จะไม่มีขีดเส้นใต้ที่มองเห็นได้โดยค่าเริ่มต้น ขีดเส้นใต้ที่คุณเห็นในโปรแกรมอ่านส่วนใหญ่ถูกวาดโดยตัวโปรแกรมอ่านเองโดยอิงจากประเภทของคำอธิบายประกอบ ไม่ใช่กลีฟใน content stream หากคุณต้องการขีดเส้นใต้ที่จับต้องได้ซึ่งคงอยู่เมื่อพิมพ์ไปยังตัวเรนเดอร์ที่ไม่ตอบสนอง หรือการแปลง PDF เป็นรูปภาพ ให้วาดมันอย่างชัดเจนด้วย LineTo และ Stroke ที่ระยะออฟเซต Y ที่เหมาะสมใต้เส้นฐานข้อความ นั่นคือการวาดแยกต่างหาก ไม่ใช่สิ่งที่ PrintHyperlink จัดการให้คุณ

API ไฮเปอร์ลิงก์ที่แสดงในที่นี้เป็นส่วนหนึ่งของ HotPDF Component สำหรับ Delphi และ C++Builder

เลือกเมธอดตามชนิดพื้นที่ที่ต้องการ: PrintHyperlink เหมาะกับข้อความพร้อม URI, AddURILink สร้าง hotspot เหนือกราฟิกใด ๆ และ AddGoToLink ใช้การนำทางภายในเอกสาร ส่วนการทดสอบควรตรวจทั้งโปรแกรมอ่านที่รองรับ annotation เต็มรูปแบบและตัวแสดงผลที่ผ่อนปรนกว่า