สตริงที่มองเห็นได้ทุกตัวในเอกสาร HotPDF ล้วนมาจากการเรียกใช้งานเพียงครั้งเดียว: TextOut(X, Y, angle, Text) ตัวอย่าง Hello World ใช้งานฟังก์ชันนี้ในรูปแบบที่เรียบง่ายที่สุด โดยตั้งค่าฟอนต์เพียงครั้งเดียวและปล่อยให้อาร์กิวเมนต์ทั้งสี่ใช้ค่าเริ่มต้นที่เหมาะสม แต่เมื่อผ่านหน้าแรกไปแล้ว อาร์กิวเมนต์ทั้งสี่ตัวนี้จะเป็นตัวรับภาระทั้งหมดของการจัดเค้าโครง อาร์กิวเมนต์ตัวที่สามใช้สำหรับหมุนข้อความ การตั้งค่าฟอนต์ก่อนหน้านั้นจะเป็นตัวกำหนดขนาดและสไตล์ ส่วนพิกัด X, Y ซึ่งวัดจากมุมของหน้ากระดาษในหน่วยพอยต์ เป็นสิ่งเดียวที่กั้นระหว่างรายงานที่ดูสะอาดตา กับข้อความที่ซ้อนทับกัน ขาดหาย หรือเลื่อนต่ำลงไปอีกบรรทัดเมื่อพิมพ์ด้วยเครื่องพิมพ์ของคนอื่น นี่คือจุดที่ TextOut แสดงคุณค่าอย่างแท้จริง และเป็นจุดที่ค่าเริ่มต้นเริ่มไม่เพียงพออีกต่อไป
ซิกเนเจอร์ (signature) ของฟังก์ชันนี้เป็นสิ่งที่ควรจำให้ขึ้นใจก่อนสิ่งอื่นใด: X และ Y เป็น Single ในหน่วยพอยต์, angle เป็น Extended ในหน่วยองศา และ Text เป็น WideString ดังนั้นจึงสามารถส่งผ่าน Unicode ได้โดยไม่ต้องมีการเรียกใช้งานแยกต่างหาก โอเวอร์โหลด (overload) ที่สองจะรับ PWORD พร้อมความยาวสำหรับกรณีที่คุณมีรหัส glyph อยู่แล้ว แต่สำหรับสตริงทั่วไป รูปแบบ WideString คือสิ่งที่คุณจะได้หยิบมาใช้
ขนาดและสไตล์มาจาก SetFont ไม่ใช่ TextOut
TextOut ไม่มีพารามิเตอร์สำหรับกำหนดขนาด ขนาด น้ำหนัก ความเอียง ทั้งหมดนี้อยู่ในการเรียกใช้ SetFont ที่เกิดขึ้นก่อนหน้านั้น และจะมีผลบังคับใช้ไปจนกว่าจะมีการเรียก SetFont ครั้งถัดไปมาแทนที่ นี่คือความจริงเพียงข้อเดียวที่อธิบายความสับสนส่วนใหญ่ในการใช้งานวันแรก: บรรทัดหนึ่งแสดงผลเป็นตัวหนา เพราะการเรียกใช้สามครั้งก่อนหน้านั้นมีการตั้งค่า [fsBold] ไว้และไม่มีอะไรมาล้างค่านั้น
Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report'); // 24pt regular
Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue'); // 12pt bold
Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands'); // 11pt italic
Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, ' +18.4% YoY'); // styles combine
อาร์กิวเมนต์ตัวที่สองคือเซตของ TFontStyles ดังนั้น [fsBold, fsItalic] คือตัวหนาเอียง และ [] คือตัวปกติ ขนาดมีหน่วยเป็นพอยต์ ซึ่งเป็นหน่วยเดียวกับพิกัด ทำให้ง่ายต่อการคำนวณระยะห่างแนวตั้ง: บรรทัดขนาด 12 พอยต์ต้องการระยะห่างแนวตั้งประมาณ 14 ถึง 16 พอยต์เพื่อให้ดูไม่อึดอัด ดังนั้นการลดค่า Y ลงทีละ 14 ต่อบรรทัดจึงเป็นระยะห่างระหว่างบรรทัด (leading) เริ่มต้นที่สมเหตุสมผล ระบบไม่มีการขึ้นบรรทัดใหม่ให้โดยอัตโนมัติ คุณต้องคำนวณเส้นฐาน (baseline) แต่ละเส้นด้วยตัวเอง ซึ่งอาจจะน่าเบื่อสำหรับย่อหน้ายาว ๆ แต่มันแม่นยำมากสำหรับแบบฟอร์ม ที่ซึ่งทุกเขตข้อมูลต้องอยู่ในพิกัดที่ตายตัว
ข้อสังเกตเชิงปฏิบัติสองประการเกี่ยวกับชื่อฟอนต์ ระบบจะค้นหาจากฟอนต์ที่ติดตั้งอยู่บนเครื่องที่ใช้คอมไพล์โปรแกรม (build machine) และสิ่งที่ระบบปฏิบัติการส่งกลับมาคือสิ่งที่จะถูกฝังลงไป ดังนั้นชื่อฟอนต์ที่พบในเครื่องเดสก์ท็อปของคุณ กับชื่อฟอนต์ที่พบบนเซิร์ฟเวอร์สำหรับคอมไพล์ จึงไม่รับประกันว่าจะเป็นฟอนต์รูปแบบเดียวกันเสมอไป และฟอนต์นั้นจะต้องครอบคลุมชุดตัวอักษรที่มีในสตริงด้วย การแสดงผลข้อความภาษาซีริลลิก (Cyrillic) หรือ CJK ภายใต้ฟอนต์ที่มีเฉพาะภาษาละติน จะแสดงผลเป็นกล่องสี่เหลี่ยมเนื่องจากไม่มี glyph โดยไม่มีการแจ้งข้อผิดพลาดใด ๆ ซึ่งนี่คือเหตุผลที่หน้า Hello World เลือกใช้ฟอนต์ Unicode ที่ครอบคลุมในกรณีที่มีการผสมหลายภาษา
อาร์กิวเมนต์ angle ใช้สำหรับการหมุนรอบจุดยึด (anchor)
อาร์กิวเมนต์ตัวที่สามเป็นสิ่งที่โค้ดส่วนใหญ่มักจะปล่อยให้เป็นศูนย์ไปตลอดกาล หากส่งค่าที่ไม่ใช่ศูนย์ ข้อความจะหมุนทวนเข็มนาฬิการอบจุดยึด (X, Y) ของตัวเอง ซึ่งก็คือมุมซ้ายล่างของข้อความ ตามจำนวนองศานั้น จุดยึดนั้นจะไม่ขยับ ดังนั้นพิกัดเดียวกับที่วางป้ายกำกับแนวนอน ก็จะถูกใช้ในการวางป้ายกำกับเวอร์ชันที่ถูกหมุนเช่นกัน เพียงแค่ทิศทางของการจัดเรียง glyph เท่านั้นที่เปลี่ยนไป
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);
// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');
// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');
// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');
กรณีที่พบบ่อยคือ 90 องศา สำหรับป้ายกำกับที่วิ่งขึ้นตามด้านข้างของแผนภูมิ หรือชื่อเรื่องบนสันหนังสือ ส่วน 45 องศาใช้จัดการกับส่วนหัวของคอลัมน์ที่เอียง ซึ่งเป็นเทคนิคที่ช่วยให้ป้ายกำกับที่กว้างสามารถวางอยู่บนคอลัมน์แคบ ๆ ได้โดยไม่ล้นไปทับส่วนอื่น การหมุนจะไม่เปลี่ยนแปลงวิธีการตีความของจุดยึด ซึ่งมักทำให้หลายคนสับสน: ข้อความที่หมุน 90 องศายังคงเริ่มต้นที่ (X, Y) และยาวขึ้นไปจากจุดนั้น ดังนั้นในการจัดกึ่งกลางป้ายกำกับที่หมุนแล้ว คุณต้องปรับจุดยึด ไม่ใช่มุม เมื่อข้อความที่ถูกหมุนหลายรายการใช้เส้นฐานเดียวกัน ให้ใช้ค่า Y เดียวกันแล้วปรับค่า X เหมือนกับที่คุณจะปรับค่า Y สำหรับบรรทัดแนวนอนที่ซ้อนกัน
การจัดวางพิกัดโดยไม่ต้องเดา
พิกัดคือส่วนที่จะผ่านการตรวจสอบ หรืออาจล้มเหลวอย่างเงียบ ๆ HotPDF วัดจากมุมซ้ายล่างของหน้ากระดาษ ค่า Y จะเพิ่มขึ้นในทิศทางขึ้นบน มีหน่วยเป็นพอยต์ โดย 72 พอยต์เท่ากับหนึ่งนิ้ว หน้ากระดาษขนาด US Letter มีขนาด 612 คูณ 792 พอยต์ ส่วน A4 คือ 595 คูณ 842 ขอบบนขนาดหนึ่งนิ้วในหน้า Letter จึงทำให้เส้นฐานแรกของคุณอยู่ใกล้กับ Y = 792 ลบ 72 ลบด้วยขนาดฟอนต์ ไม่ใช่ค่าตัวเลขน้อย ๆ ที่อยู่ใกล้ขอบบน ใครก็ตามที่คุ้นเคยกับพิกัดบนหน้าจอ ซึ่งค่า Y จะเพิ่มขึ้นลงด้านล่างโดยเริ่มจากศูนย์ มักจะเขียนบรรทัดแรกทะลุขอบล่างออกไป แล้วเสียเวลาอีกสิบนาทีเพื่อสงสัยว่ามันหายไปไหน
จงจัดการกับเค้าโครงด้วยการคำนวณทางคณิตศาสตร์จากจุดยึดที่มีการตั้งชื่อ แทนที่จะใช้คอลัมน์ของตัวเลขลึกลับ (magic numbers) ขอบซ้าย เส้นฐานที่จะลดค่าลงต่อบรรทัด และระยะห่างบรรทัดแบบตายตัว จะเปลี่ยนกลุ่มของป้ายกำกับให้กลายเป็นการวนลูปสั้น ๆ แทนที่จะเป็นชุดค่าคงที่ที่เรียงกันเป็นกำแพง:
const
LeftMargin = 72; // 1 inch in
TopBaseline = 720; // first line, ~1 inch down on Letter
Leading = 16; // vertical step between lines
var
Y: Single;
Line: string;
begin
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := TopBaseline;
for Line in ReportLines do
begin
Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
Y := Y - Leading;
if Y < 72 then // bottom margin reached
begin
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 11); // font resets on a new page
Y := TopBaseline;
end;
end;
end;
การป้องกันการขึ้นหน้าใหม่คือสิ่งที่ทุกคนมักจะลืมเป็นอันดับแรกและเป็นจุดที่ก่อปัญหามากที่สุด TextOut ไม่มีการจัดการเค้าโครงแบบไหล (flow layout) อยู่เบื้องหลัง หากคุณลดค่าต่ำกว่าขอบล่าง ข้อความก็จะยังคงวาดลงไปในช่องว่าง ทะลุหน้ากระดาษ ออกไปสู่อะไรก็ไม่รู้ โดยไม่มีการเตือนใด ๆ ดังนั้นคุณต้องตรวจสอบค่า Y ด้วยตัวเอง เรียก AddPage เมื่อค่านั้นผ่านขอบล่าง แล้วรีเซ็ตเส้นฐาน การเรียก SetFont หลัง AddPage ไม่ใช่สิ่งที่ทำเผื่อเลือก: ฟอนต์ปัจจุบันจะไม่คงอยู่หลังจากขึ้นหน้าใหม่ และข้อความแรกในหน้าใหม่จะถูกแสดงด้วยฟอนต์เริ่มต้นของโปรแกรมอ่าน PDF หากคุณข้ามขั้นตอนนี้ไป
ระยะห่างอักขระและคำเพื่อความพอดีและการจัดแนว
บางครั้งสตริงอาจถูกต้องแต่ความกว้างไม่เหมาะสม: ส่วนหัวที่ต้องขยายให้ครอบคลุมเส้นที่กำหนดไว้ รหัสที่ควรอ่านตัวเลขให้ดูโปร่งขึ้น คอลัมน์ที่ต้องการการขยับค่าเล็กน้อยเพื่อจัดแนว รูปแบบ PDF มีตัวดำเนินการสถานะข้อความสองตัวสำหรับเรื่องนี้ คือ ระยะห่างอักขระ (Tc, พื้นที่เพิ่มเติมที่เพิ่มหลังแต่ละ glyph) และระยะห่างคำ (Tw, พื้นที่เพิ่มเติมที่เพิ่มขึ้นในแต่ละช่องว่าง) และทั้งสองถูกแสดงในหน่วยของพื้นที่ข้อความที่ไม่มีการปรับสัดส่วน ซึ่งก็คือหน่วยพอยต์ตามขนาดฟอนต์ปัจจุบัน ค่าเหล่านี้เป็นสถานะ ไม่ใช่อาร์กิวเมนต์ที่ส่งไปให้ TextOut ดังนั้นคุณต้องตั้งค่า วาดข้อความ แล้วตั้งค่ากลับ
// Letter-space a short heading so it stretches across a rule.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0); // reset before normal body text
// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name Department Extension');
Pdf.CurrentPage.SetWordSpacing(0);
ระยะห่างคำจะมีผลเฉพาะกับอักขระช่องว่าง (รหัส 32) ซึ่งมีผลลัพธ์ที่น่ารู้: มันจะไม่มีผลใด ๆ ภายในข้อความ CJK ที่ไม่มีช่องว่าง ASCII และมันจะตอบสนองอย่างแปลกประหลาดกับข้อความที่เข้ารหัสด้วยดัชนี glyph แทนที่จะเป็นไบต์ สำหรับผลลัพธ์แบบตารางของอักษรละติน มันเป็นวิธีง่ายๆ ในการขยายช่องว่างโดยไม่ต้องพิมพ์สตริงใหม่ การกำหนดระยะห่างอักขระเป็นเครื่องมือที่ดีกว่าสำหรับส่วนหัวที่ต้องมีความกว้างตรงตามเป้าหมาย เนื่องจากมันจะกระจายการปรับแต่งอย่างเท่าเทียมกันในทุก glyph แทนที่จะไปรวมอยู่แค่ตรงช่องว่าง
การรีเซ็ตคือกฎเหล็กทั้งหมด ระยะห่างก็เหมือนกับฟอนต์ ซึ่งเป็นส่วนหนึ่งของสถานะการวาดบนหน้ากระดาษ และสถานะจะคงอยู่ไปจนกว่าคุณจะเปลี่ยนมัน หากเพิ่มช่องว่างอักขระให้ส่วนหัวเดียวแล้วลืมกำหนดค่ากลับเป็นศูนย์ ทุกย่อหน้าถัดจากนั้นจะสืบทอดการขยายนี้ไป ทำให้ดูมีความผิดปกติเล็กน้อยที่ระบุได้ยาก ซึ่งอาจจะรอดจากการตรวจทานแบบผ่าน ๆ แต่จะไม่ผ่านการตรวจสอบอย่างละเอียด นิสัยที่เชื่อถือได้คือการตั้งค่าระยะห่าง วาดข้อความที่ต้องการใช้ค่านั้น แล้วตั้งค่ากลับเป็นศูนย์ในบรรทัดถัดไป ดังนั้นโค้ดส่วนหลังจึงไม่จำเป็นต้องรู้ว่าโค้ดส่วนก่อนหน้าได้ทำอะไรไปบ้าง
การตรวจสอบผลลัพธ์ในจุดที่อาจเกิดปัญหาจริง
เค้าโครงข้อความมักจะมีปัญหาเมื่อนำไปใช้ในเครื่องที่สอง ไม่ใช่เครื่องแรก ดังนั้นการตรวจสอบที่สำคัญควรเกิดจากนอกโต๊ะทำงานของคุณ ลองเปิดไฟล์ที่สร้างขึ้นบนระบบที่ไม่ได้ติดตั้งฟอนต์สำหรับนักพัฒนา และยืนยันว่าฟอนต์ที่ถูกฝังยังคงแสดงผลได้ รวมถึงอักษรละตินที่มีเครื่องหมาย (accented) อักษรที่ไม่ใช่ละติน และเครื่องหมายวรรคตอน โดยตรวจให้ครอบคลุมในการเปิดดูเพียงครั้งเดียว แทนที่จะสุ่มตรวจเฉพาะอักขระที่ดูง่าย ลองเลือกและคัดลอกข้อความสักสองสามบรรทัดเพื่อยืนยันว่าข้อความนั้นเป็นข้อความจริงๆ ไม่ใช่เส้นโครงร่าง (outlines) ซึ่งเป็นสิ่งสำคัญเมื่อต้องมีการค้นหาหรือสกัดข้อมูล ลองป้อนข้อมูลตัวอย่างที่เป็นตัวแทนจริงๆ ลงในเค้าโครง เช่น ป้ายกำกับภาษาเยอรมันที่ยาวที่สุดและตัวเลขที่กว้างที่สุด แทนที่จะใช้ข้อความตัวแทนที่เรียบร้อย เพราะข้อความที่จะล้นออกนอกเขตข้อมูลมักจะเป็นข้อความที่คุณไม่ได้พิมพ์เองกับมือ และถ้านั้นต้องถูกพิมพ์ลงบนแบบฟอร์มที่มีการพิมพ์ไว้ล่วงหน้า (preprinted form) ให้ลองพิมพ์หรือทำแรสเตอร์ (rasterize) ตัวอย่างหนึ่งแผ่นแล้วนำไปทาบกับต้นฉบับ; การที่เส้นฐานเลื่อนไปเพียงหนึ่งในสี่มิลลิเมตรอาจจะมองไม่เห็นบนหน้าจอ แต่มันจะเห็นได้ชัดเจนบนกระดาษ
หากคุณยังไม่ได้เขียนหน้าใดเลย ให้เริ่มต้นด้วย ตัวอย่าง HotPDF Hello World ซึ่งจะแนะนำการตั้งค่าเอกสาร ฟอนต์ และระบบพิกัดมุมซ้ายล่างที่ทุกสิ่งที่กล่าวมาข้างต้นต้องพึ่งพา การเรียกใช้ TextOut, SetFont และระยะห่างที่แสดงในที่นี้เป็นส่วนหนึ่งของ HotPDF Component สำหรับ Delphi และ C++Builder