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

การติดแท็กโครงสร้างอัตโนมัติเพื่อ PDF ที่เข้าถึงได้ใน Delphi

PDFlibPas สามารถติดแท็กเอกสารไปพร้อมกับการวาด เปิด SetAutoTagMode แล้วคำสั่ง DrawText ธรรมดาจะกลายเป็นย่อหน้า ข้อความที่วาดติดต่อจาก RegisterHeading จะกลายเป็นหัวเรื่องระดับนั้น ส่วนหัวและส่วนท้ายแบบ running จะกลายเป็น artifact ที่ตัวอ่านข้ามไป รูปภาพจะกลายเป็น figure และ DrawTableRows จะพาตารางพร้อมแถวและเซลล์เข้าไปอยู่ใน structure tree

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

บิตโหมดครอบคลุมอะไรบ้าง

SetAutoTagMode รับ bit mask แล้วคืนค่าโหมดที่เคยใช้อยู่ก่อน AUTOTAG_TEXT (1) ติดแท็กข้อความเป็นย่อหน้า หรือเป็นหัวเรื่องเมื่อถึงคิว AUTOTAG_FURNITURE (2) ทำเครื่องหมายส่วนหัวและส่วนท้ายแบบ running รวมถึงเลขหน้าเป็น artifact AUTOTAG_FIGURE (4) เปลี่ยนรูปที่วาดเป็น figure หรือเป็น artifact เมื่อประกาศไว้ว่าเป็นรูปประดับ AUTOTAG_TABLE (8) พาตารางที่วาดเข้าไปใน structure tree AUTOTAG_DEFAULT คือ 15 คือทั้งสี่อย่างรวมกัน

การเปิดโหมดยังทำเครื่องหมายเอกสารว่าเป็น tagged อีกด้วย และขั้นตอนนี้สำคัญมากกว่าที่เสียงมันฟังดู ตัวอ่านจะถือว่าเอกสารเป็น untagged เว้นแต่ catalog จะบอกเป็นอย่างอื่น (ISO 32000-1 §14.7.1) ดังนั้นไฟล์ที่พก structure tree ครบถ้วนแต่ไม่มีการประกาศ /MarkInfo จะถูกเทคโนโลยีช่วยเหลือประกาศว่าไม่มีโครงสร้างเลย ทั้งที่ต้นไม้นั้นอยู่ตรงนั้น เพียงแต่ไม่มีอะไรอ่านมัน

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.SetAutoTagMode(AUTOTAG_DEFAULT);   // text + furniture + figures + tables
    Lib.AddStandardFont(4);
    Lib.SetTextSize(18);
    Lib.RegisterHeading(1, 'Annual service report');
    Lib.DrawText(72, 96, 'Annual service report');   // becomes H1
    Lib.SetTextSize(11);
    Lib.DrawText(72, 130, 'Every unit installed before 2024 was inspected.');
    Lib.SaveToFile('report.pdf');
  finally
    Lib.Free;
  end;
end;

หัวเรื่องรู้ได้อย่างไรว่าข้อความใดเป็นของมัน

RegisterHeading กำหนดระดับให้ข้อความถัดไปที่วาด แล้วรอข้อความนั้น หากมีรูปวาดแทรกเข้ามาระหว่างทาง รูปนั้นจะกลายเป็น figure และหัวเรื่องยังคงค้างอยู่รอข้อความที่ตามมา พฤติกรรมนี้ตั้งใจไว้แบบนั้น เพราะทางเลือกอื่นที่ให้รูปรับระดับหัวเรื่องไป ได้ผลิตเอกสารที่เส้นขีดตกแต่งใต้ชื่อเรื่องถูกประกาศเป็นชื่อเรื่อง

กฎ "ใช้ไปกับรายการเดียว" เดียวกันนี้ควบคุม figure RegisterFigure ส่งคำบรรยายให้รูปถัดไป ส่วน RegisterDecoration ประกาศรูปถัดไปเป็นเส้นขีด ขอบ หรือพื้นหลังที่ไม่มีความหมาย ทั้งสองถูกใช้ไปกับรูปเดียว ดังนั้นรูปที่มาทีหลังจะไม่สืบทอดคำบรรยายที่ตั้งใจให้รูปก่อนหน้า — ซึ่งเป็นสาเหตุที่ alt text มักไปติดอยู่กับรูปผิดในโค้ดที่ติดแท็กด้วยมือ

คำบรรยายสำคัญกว่าสตริงอื่นใดในเอกสารที่เข้าถึงได้ ผู้อ่านตาบอดได้รับคำบรรยายแทนรูป และนั่นคือทั้งหมดที่พวกเขาได้ "Chart" ไม่ใช่คำบรรยาย "Quarterly revenue by region, with the eastern region highest in Q3" ต่างหากที่ใช่

Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0);      // becomes a tagged Figure

Lib.RegisterDecoration;                       // meaningless rule
Lib.AddImageFromFile('divider.png', 0);       // drawn inside a layout artifact

ตาราง ส่วนหัว และจุดที่การตัดสินใจเรื่องการทำซ้ำอยู่

เมื่อเปิดบิตตาราง DrawTableRows จะพาตารางพร้อมแถวและเซลล์เข้าไปใน structure tree ทำให้ตัวอ่านบอกได้ว่าค่าหนึ่งอยู่ในคอลัมน์ใด แทนที่จะอ่านตารางทั้งหมดออกมาเป็นข้อความรวมที่ไม่เกี่ยวข้องกัน SetTableHeaderRowCount กำหนดจำนวนแถวนำที่เป็นส่วนหัว แถวเหล่านั้นจะถูกเขียนเป็นเซลล์หัวข้อมือพก scope คอลัมน์ ซึ่งเป็นสิ่งที่ทำให้ตัวอ่านประกาศหัวข้อของค่าที่ผู้ใช้อยู่ได้

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

var
  TableID: Integer;
begin
  TableID := Lib.CreateTable(40, 3);
  Lib.SetTableHeaderRowCount(TableID, 1);       // row 1 is the header band
  Lib.SetTableCellContent(TableID, 1, 1, 'Part');
  Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
  Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
  // ... fill the data rows ...
  // Draw rows 1..40 into a 600pt band, repeating one header row per page
  Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;

ผสมการติดแท็กอัตโนมัติกับด้วยมือ

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

กฎความปลอดภัยสองข้อรักษาเอาต์พุตให้สะอาด ไม่มีอะไรถูกติดแท็กภายใน artifact เพราะเนื้อหาที่ทำเครื่องหมายเป็น artifact ต้องไม่พกองค์ประกอบโครงสร้าง และข้อความว่างไม่เปิดองค์ประกอบใด ดังนั้น DrawText ที่หลุดมาพร้อมสตริงว่างจะไม่สร้างองค์ประกอบโครงสร้างที่ตัวอ่านจะประกาศออกมาเป็นความว่างเปล่า ทั้งสองอย่างคือประเภทของข้อบกพร่องที่เอกสารติดแท็กด้วยมือสะสมเงียบ ๆ และตัวตรวจสอบรายงานเป็นกลุ่มเดือนต่อมา

สิ่งที่การติดแท็กอัตโนมัติยังไม่ตัดสินใจให้คุณ

ลำดับการอ่านที่อยู่นอกเหนือลำดับการวาด บทบาทเชิงความหมายที่ไม่ใช่ย่อหน้า หัวเรื่อง figure หรือตาราง และการประกาศภาษา การติดแท็กอัตโนมัติกำหนดโครงสร้างตามลำดับที่เนื้อหาถูกวาด — หากโค้ดเลย์เอาต์ของคุณวาด sidebar ก่อนเนื้อหา นั่นคือลำดับที่ต้นไม้บันทึก สำหรับเอกสารที่ลำดับการมองเห็นกับลำดับการอ่านแตกต่างกันอย่างแท้จริง API ติดแท็กด้วยมือยังคงเป็นเครื่องมือที่ถูกต้อง และคู่มือ tagged PDF และโครงสร้างการเข้าถึง ครอบคลุมบทบาท scope และการผูกส่วนหัวอย่างละเอียด

เมื่อเอกสารเสร็จสมบูรณ์ ให้ตรวจสอบแทนที่จะสมมติเอา บันทึกของ PDF/A และ PDF/UA preflight แสดงวิธีรับคำตัดสินเกี่ยวกับโครงสร้างที่คุณผลิตออกมา และคู่มือ การส่งออกรายงานขับเคลื่อนด้วยข้อมูล ครอบคลุมจุดที่คำสั่งเหล่านี้สอดสนิทในเอนจินรายงานที่สร้างเลย์เอาต์จากข้อมูล

PDFlibPas เป็นไลบรารี PDF แบบ Pascal พื้นเมืองสำหรับ Delphi, C++Builder และ Lazarus โดยไม่มีรันไทม์ PDF ภายนอก เอาต์พุตที่เข้าถึงได้จึงผลิตจากโค้ดชุดเดียวกับที่วาดเอกสาร — ดู หน้าผลิตภัณฑ์ PDFlibPas สำหรับ API และรายการแพลตฟอร์มฉบับสมบูรณ์