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

คำอธิบายประกอบ PDF ใน Delphi ด้วย HotPDF: ประเภทและ Rects

คำอธิบายประกอบ (annotation) ไม่ใช่เนื้อหาของหน้ากระดาษ เมื่อคุณเรียกใช้ TextOut หรือวาดสี่เหลี่ยม รอยขีดเขียนเหล่านั้นจะกลายเป็นส่วนหนึ่งของ content stream ของหน้ากระดาษ ซึ่งฝังอยู่ในไบต์ที่ตัวเรนเดอร์วาด คำอธิบายประกอบคือพจนานุกรมที่แยกออกมาต่างหาก โดยจะเชื่อมต่อกับหน้ากระดาษผ่านอาร์เรย์ /Annots มีสี่เหลี่ยม รูปร่างลักษณะ และวงจรชีวิตเป็นของตัวเอง ผู้อ่านสามารถเปิด เลื่อน ซ่อน หรือลบออกได้โดยไม่ต้องแตะกลีฟของหน้ากระดาษนั้นเลย การแยกส่วนนี้คือเหตุผลทั้งหมดว่าทำไมคำอธิบายประกอบจึงมีอยู่ และมันยังเป็นต้นเหตุของสองสิ่งที่มักทำให้คนประหลาดใจเป็นอย่างแรก: ตำแหน่งที่คำอธิบายประกอบไปตกอยู่ และหน้าตาของมันเมื่อโปรแกรมอ่านบางตัวเรียกใช้งาน

HotPDF เปิดใช้งานประเภทย่อย (subtypes) ของคำอธิบายประกอบตามมาตรฐาน ISO 32000 ผ่านกลุ่มคำสั่ง AddXxxAnnotation บนออบเจกต์หน้ากระดาษ ทั้งหมดนี้มีโครงสร้างเดียวกัน: สี่เหลี่ยมที่กำหนดจุดอ้างอิงให้คำอธิบายประกอบบนหน้ากระดาษใน PDF user space ข้อมูลที่บรรจุอยู่ (ข้อความ, ชื่อของตราประทับ, หรือจุดพิกัดคู่) และสี หากคุณกำหนดสี่เหลี่ยมได้ถูกต้อง งานส่วนใหญ่ก็จะเสร็จสมบูรณ์ ที่เหลือก็คือการรู้ว่าประเภทย่อยใดที่มาพร้อมกับรูปลักษณ์ของตัวเอง และประเภทใดที่ต้องพึ่งพาให้โปรแกรมอ่านเป็นตัววาดให้

หน้า PDF ที่สร้างโดย HotPDF แสดงไอคอนบันทึกข้อความ กล่องข้อความอิสระ การมาร์กรูปสี่เหลี่ยมจัตุรัสและเส้น และตราประทับอนุมัติที่วางกระจายอยู่ทั่วหน้า
หน้ากระดาษหนึ่งหน้าที่มีคำอธิบายประกอบหลายประเภทพร้อมกัน: บันทึกข้อความ ข้อความอิสระ รูปทรงเรขาคณิต และตราประทับ

สี่เหลี่ยมคือคำอธิบายประกอบ ไม่ใช่ข้อความ

ทุกการเรียกคำอธิบายประกอบต้องใช้ TRect และสี่เหลี่ยมนั้นจะมีความหมายต่างจากพิกัดที่คุณส่งให้ TextOut สำหรับบันทึกข้อความ (text note) มันคือฮอตสปอตที่คลิกได้ ซึ่งเป็นพื้นที่เล็ก ๆ ที่มีไอคอนบันทึกตั้งอยู่ และเมื่อคลิก คอมเมนต์ก็จะเด้งเปิดขึ้นมา สำหรับกล่องรูปสี่เหลี่ยมจัตุรัสหรือข้อความอิสระ มันคือขอบเขตที่มองเห็นได้ของการมาร์ก สำหรับตราประทับ มันคือกล่องที่ภาพตราประทับจะถูกปรับสเกลให้พอดี ตัวเลขเหล่านี้คือพอยต์ใน PDF user-space วัดจากมุมซ้ายล่างของหน้ากระดาษ โดย Y เพิ่มขึ้นไปด้านบน ซึ่งเป็นข้อกำหนดเดียวกับที่ส่วนที่เหลือของ HotPDF ใช้

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

Pdf.CurrentPage.AddTextAnnotation(
  'Reviewer: confirm the totals on this line before sign-off.',
  Rect(120, 700, 140, 720),   // icon hotspot, ~20pt square
  False,                      // closed until the reader clicks it
  taComment,                  // bubble icon
  clBlue);

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

ชื่อไอคอนมาจาก THPDFTextAnnotationType ซึ่งแมปกับไอคอนบันทึกมาตรฐาน: taComment, taKey, taNote, taHelp, taParagraph, taNewParagraph และ taInsert ไอคอนเป็นสิ่งเดียวที่ประเภทนี้สามารถเปลี่ยนได้ มันไม่เปลี่ยนพฤติกรรมการทำงาน และคุณควรรู้ด้วยว่าไม่ใช่โปรแกรมอ่านทุกตัวจะวาดไอคอนครบทั้งเจ็ดแบบ ไอคอนที่ปลอดภัยที่ใช้ได้ทั้งกับโปรแกรมอ่านเก่าและใหม่คือ taComment, taNote และ taHelp

ข้อความอิสระเขียนลงบนหน้ากระดาษ แต่ยังคงเป็นคำอธิบายประกอบ

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

Pdf.CurrentPage.AddFreeTextAnnotation(
  'DRAFT - not for distribution',
  Rect(200, 210, 400, 235),   // the box the text is laid into
  ftCenter,                   // ftLeftJust / ftCenter / ftRightJust
  clRed);

ความสำคัญของพื้นที่รูปสี่เหลี่ยมในจุดนี้มีมากกว่าโน้ตแบบข้อความ เนื่องจากข้อความต้องถูกจัดให้อยู่ภายในขอบเขตสี่เหลี่ยมนี้พอดี ถ้ารูปแบบกล่องข้อความสั้นเกินไป ข้อความจะถูกตัดทิ้งที่ขอบล่าง ถ้าแคบเกินไป ข้อความก็จะถูกตัดขึ้นบรรทัดใหม่ในจุดที่คุณไม่ได้ตั้งใจให้เป็น ค่าการจัดแนวมาจาก THPDFFreeTextAnnotationJust ซึ่งมีอยู่ 3 รูปแบบด้วยกัน และเนื่องจากข้อความอิสระเป็นเพียงรูปแบบของคำอธิบายประกอบ(markup annotation) ผู้อ่านที่เปิดเอกสารด้วยโปรแกรมปรับแต่งจะสามารถเลือก ย้าย หรือลบมันออกได้แบบอิสระ ซึ่งเป็นความต่างที่ทำให้คุณสามารถเลือกได้ว่าจะใช้ข้อความอิสระ หรือว่าจะแค่วาดข้อความขึ้นมาเฉย ๆ ด้วยคำสั่ง TextOut หากคุณต้องการให้ป้ายกำกับนั้นอยู่ถาวร ก็แค่วาดมันขึ้นมา แต่ถ้ามันเป็นส่วนปรับแต่งที่มีเจตนาว่าจะสามารถดึงออกทีหลังได้ ให้ใช้คำอธิบายประกอบแทน

เรขาคณิตและเส้นขีดสำหรับชี้ไปยังจุดต่าง ๆ

สี่เหลี่ยม วงกลม และเส้น เป็นเพียงการขีดเขียนที่คุณใช้ชี้ไปยังจุดต่าง ๆ บนเอกสาร แทนการอธิบายจุดนั้นเป็นคำพูด AddCircleSquareAnnotation ครอบคลุมรูปร่างกล่องสองรูปแบบผ่าน THPDFCSAnnotationType คือ csCircle หรือ csSquare โดยมีสี่เหลี่ยมเป็นตัวบอกขอบเขตของรูปทรงนั้น

// A box drawn around a figure that needs attention
Pdf.CurrentPage.AddCircleSquareAnnotation(
  'Check this region against the source data',
  Rect(50, 300, 120, 360),
  csSquare,
  clGreen);

// A line, given two points rather than a rectangle
var
  StartPt, EndPt: THPDFCurrPoint;
begin
  StartPt.X := 130; StartPt.Y := 360;
  EndPt.X   := 250; EndPt.Y   := 320;
  Pdf.CurrentPage.AddLineAnnotation(
    'Points from the note to the figure',
    StartPt, EndPt,
    clBlue);
end;

จะเห็นได้ว่าคำอธิบายประกอบแบบเส้นจะหลีกหนีรูปแบบสี่เหลี่ยม: โดยมันใช้ระเบียน THPDFCurrPoint สองค่า เป็นจุดเริ่มต้นและจุดสิ้นสุด เพราะเส้นถูกกำหนดโดยจุดปลาย ไม่ใช่ขอบเขตกล่องสี่เหลี่ยม (bounding box) ส่วนสีจะกำหนดค่าสำหรับเส้น หากคุณต้องการให้มีหัวลูกศร HotPDF มีโอเวอร์โหลดของ AddLineAnnotation ที่ยอมรับลักษณะการลงท้ายเส้นบรรทัดได้ด้วย แต่หากใช้รูปแบบทั่วไปที่กำหนดแค่อาร์กิวเมนต์ 3 ตัว จะวาดเฉพาะเส้นเปล่า ซึ่งเป็นรูปแบบที่ callout ทั่วไปต้องการใช้

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

ตราประทับจำเป็นต้องใช้ตัวประมวลผล (viewer) ช่วยเรนเดอร์

คำอธิบายประกอบตราประทับเป็นรูปแบบที่คุณน่าจะได้เห็นความแตกต่างของหน้าตาจากตัวประมวลผลตัวหนึ่งไปอีกตัวได้ชัดเจนที่สุด และนี่เป็นเหตุผลที่คุ้มค่ากับการทำความเข้าใจว่า AddStampAnnotation สร้างชื่อให้กับตราประทับมาตรฐานผ่าน THPDFStampAnnotationType ด้วยค่าอย่าง satApproved, satConfidential, satFinal, satDraft และ satForComment

Pdf.CurrentPage.AddStampAnnotation(
  'Approved for release on review',
  Rect(50, 400, 200, 440),
  satApproved,
  clGreen);

ชื่อตราประทับเป็นตัวร้องขอ PDF ได้ระบุชุดของชื่อตราประทับมาตรฐานไว้แล้ว แต่ไม่รวมหน้าตาของตราประทับแต่ละอัน ดังนั้นโปรแกรมอ่าน (viewer) แต่ละตัวจึงมีตราประทับในรูปแบบการออกแบบของตัวเองที่ตรงกับ "APPROVED" หรือ "CONFIDENTIAL" และส่วนน้อยก็จะไม่ได้แสดงผลตามชื่อเลย เพราะไม่รู้จัก รูปแบบกล่องสี่เหลี่ยมจะมีส่วนควบคุมกล่องที่รูปทรงของตราประทับสามารถยืดหดได้ และสีเป็นสิ่งที่โปรแกรมอ่านสามารถเลือกได้ว่าจะตอบสนองอย่างไรก็ได้ ถ้าคุณต้องการให้ตราประทับดูเหมือนกันทุกที่ ตัวเลือกที่น่าเชื่อถือได้ก็คือการไม่ใช้ตัวเลือกของตราประทับแบบมาตรฐานเลย: ให้ใช้วิธีการวาดเครื่องหมายขึ้นมาเองด้วยคำสั่ง TextOut ไปพร้อมกับคำสั่งวาดภาพ หรือให้จัดวางโดยให้เป็นข้อความอิสระตามที่คุณกำหนดรูปลักษณ์เอาไว้เอง แต่ถ้าคุณต้องการให้รูปแบบนี้ดูคุ้นเคยกับตัวโปรแกรมอ่านของผู้ชมและคุณสามารถรับกับข้อบกพร่องที่เกิดขึ้นได้ ให้ลองเลือกใช้ตราประทับแบบมาตรฐานดู

เอกสารแนบก็เป็นอีกสิ่งที่มีสี่เหลี่ยมและข้อมูลเป็นส่วนประกอบ AddFileAttachmentAnnotation จะรับเอาคำอธิบาย เส้นทางไปยังไฟล์ที่แนบมา สี่เหลี่ยมสำหรับตัวไอคอนคลิปหนีบกระดาษ และค่าสี ไฟล์นี้จะแฝงเข้าไปพร้อมกับไฟล์ PDF และส่วนของตัวไอคอนเปรียบเสมือนด้ามจับที่เอาไว้ใช้ให้คนอ่านได้เอาไว้ดึงข้อมูลออกมา

คำอธิบายประกอบต่างจากฟิลด์ AcroForm อย่างไร

ความสับสนที่อาจต้องแลกมาด้วยความยุ่งยากมากที่สุด ก็คือการจัดการคำอธิบายประกอบราวกับว่าเป็นฟิลด์ฟอร์ม (form field) ซึ่งทั้ง 2 สิ่งนี้ต่างแนบมากับหน้าเอกสารผ่านคำสั่ง /Annots เหมือนกัน และอันที่จริงฟิลด์ฟอร์มก็คือชนิดย่อย (subtype) พิเศษของคำอธิบายประกอบนี่แหละ (หรือวิดเจ็ต) และนั่นเป็นเหตุผลว่าทำไม 2 อย่างนี้จึงดูเหมือนจะเกี่ยวข้องกัน แต่ทั้งสองนี้ไม่ได้มีคุณสมบัติที่ใช้แทนกันได้แต่อย่างใด ฟิลด์ฟอร์มคือสิ่งที่มีค่าในตัว มีชื่อ มีส่วนในการกำหนดลำดับของแถบเมนู และสามารถใช้สั่งการ ส่งคำสั่ง(submit) รีเซต (reset) หรือใช้สคริปต์ได้ คุณสามารถสร้างสิ่งเหล่านี้ได้ด้วยคำสั่ง AddTextField AddCheckBox และ AddPushButton ไม่ใช่ด้วยคำสั่งสำหรับการใช้คำอธิบายประกอบจากในหน้านี้แต่อย่างใด ในขณะที่ คำอธิบายประกอบการมาร์กอัปคือที่ที่เก็บรูปทรง หรือคอมเมนต์เอาไว้ ซึ่งนั่นไม่ได้มีมูลค่าในตัวเองเพียงพอต่อการจัดส่งให้ และยังเป็นการหยิบเครื่องมือมาใช้แบบผิดงานทันทีที่คุณต้องการรวบรวมข้อมูล

วิธีทดสอบสำหรับการใช้งานจริงนั้นก็เรียบง่ายมาก ถ้าผู้ใช้งานตั้งใจว่าจะต้องพิมพ์ข้อความลงไป เลือกตัวเลือก หรือจะคลิกและรอให้ระบบช่วยจำข้อมูลเอาไว้ คุณต้องใช้ฟิลด์ AcroForm แต่ถ้าเพียงแค่ต้องการทิ้งข้อความ มาร์กพื้นที่ หรือต้องการประทับสถานะที่จะส่งไปพร้อมกับไฟล์โดยที่ไม่ใช่ตัวข้อมูล คุณต้องใช้คำอธิบายประกอบ การใช้งานผสมกันรังแต่จะทำให้เอกสารดูปกติในทางทฤษฎีแต่จะรวนทันทีเมื่อลงมือใช้จริง: "ฟิลด์" ที่ไม่มีใครสามารถเติมอะไรลงไปได้ หรือ "คอมเมนต์" ที่เลือนหายไปทันทีเมื่อระบบถูกรีเซต ในฝั่งของการโต้ตอบกับฟิลด์รูปแบบต่าง ๆ การตรวจสอบ รวมทั้งการสั่งข้อมูลให้จัดส่ง (submit) ล้วนมีหน้าที่ของมันอยู่ในเรื่องของการสร้างฟิลด์ AcroForm และการทำงาน แล้ว

การรวมข้อมูลเข้าไว้ในหน้ากระดาษหนึ่งหน้า

ชิ้นส่วนเหล่านี้ประกอบกันในรูปแบบที่ส่วนอื่น ๆ ใน HotPDF ก็ทำ กำหนดค่าคุณสมบัติต่าง ๆ ให้กับตัวเอกสาร, สั่ง BeginDoc, วาดข้อความให้หน้ากระดาษอย่างที่คุณต้องการ ไม่ว่าจะด้วยข้อความ หรือภาพกราฟิก, เพิ่มคำอธิบายประกอบบนนั้น แล้วจบด้วยการใช้คำสั่ง EndDoc คำอธิบายประกอบเหล่านี้จะผูกอยู่กับ CurrentPage ดังนั้นเมื่อสั่ง AddPage มันก็จะไปอยู่ที่หน้าใหม่ทันที และสิ่งที่คุณหมายมั่นว่าจะโน้ตลงไปที่หน้าหนึ่งก็จะโผล่มาที่หน้าสองแบบไม่มีปี่มีขลุ่ยทันทีหากคุณไปกดเพิ่มโน้ตหลังจากพึ่งเพิ่มหน้าใหม่

Pdf := THotPDF.Create(nil);
try
  Pdf.FileName := 'annotated.pdf';
  Pdf.Compression := cmFlateDecode;
  Pdf.FontEmbedding := True;
  Pdf.BeginDoc;

  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Pdf.CurrentPage.TextOut(50, 740, 0, 'Quarterly figures, draft for review');

  Pdf.CurrentPage.AddTextAnnotation(
    'Confirm the totals before sign-off.',
    Rect(50, 720, 70, 740), False, taComment, clBlue);
  Pdf.CurrentPage.AddFreeTextAnnotation(
    'DRAFT', Rect(450, 720, 540, 745), ftCenter, clRed);
  Pdf.CurrentPage.AddStampAnnotation(
    'For comment', Rect(50, 660, 180, 695), satForComment, clGreen);

  Pdf.EndDoc;
finally
  Pdf.Free;
end;

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

คำสั่งของคำอธิบายประกอบที่แสดงให้เห็นอยู่นี้เป็นส่วนหนึ่งของ HotPDF Component สำหรับใช้งานใน Delphi และ C++Builder