ส่งประโยคภาษาอาหรับ يوضح ملف PDF هذا ให้ TextOut ธรรมดา แล้วหน้าที่ได้กลับมาจะผิดสองทางพร้อมกัน คำวิ่งจากซ้ายไปขวาแทนที่จะเป็นขวาไปซ้าย และตัวอักษรแยกกันอยู่ในรูปเดี่ยวแทนที่จะเชื่อมกันเป็นคำ ไม่มีข้อผิดพลาดใด ๆ Delphi คอมไพล์ผ่าน ไฟล์เปิดได้ และผู้ตรวจที่อ่านภาษาอาหรับออกจะบอกคุณว่าผลลัพธ์ใช้ไม่ได้ ทางแก้คือการเรียกเมธอดหนึ่งตัว ไม่ใช่การเปลี่ยนไลบรารี: HotPDF ส่งข้อความจากขวาไปซ้ายผ่านเมธอดแยกต่างหากชื่อ RtLTextOut ที่จัดการการเรียงลำดับใหม่ซึ่ง TextOut ธรรมดาไม่ทำให้ หน้านี้คือคู่มืออ้างอิงที่ใช้งานได้จริงของเมธอดนั้น: ลายเซ็นฟังก์ชันและพารามิเตอร์ อาร์กิวเมนต์ charset ที่เลือกระบบเขียน ผลข้างเคียงระดับเอกสาร การตั้งค่าฟอนต์ที่ต้องมาก่อน และความล้มเหลวที่ส่งถึงฝ่ายสนับสนุนจริง ๆ พร้อมทางแก้ของแต่ละข้อ
ลายเซ็นฟังก์ชันและพารามิเตอร์
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: PWORD; TextLength: Integer); overload;
X และ Y ยึดตำแหน่งของช่วงข้อความในระบบพิกัดของหน้าเอง วัดจากมุมซ้ายล่างโดยค่า Y เพิ่มขึ้นเมื่อขึ้นบน เป็นจุดกำเนิดเดียวกับที่ทุกการเรียก TextOut ใช้ RtLTextOut เปลี่ยนลำดับของกลีฟ ไม่ได้เปลี่ยนจุดที่หน้าเริ่มวัด angle หมุนเส้นฐานเหมือนกับที่ทำใน TextOut ทุกประการ ค่า 0 จึงวาดเป็นบรรทัดแนวนอน Text คือสตริงในลำดับตรรกะ ลำดับเดียวกับที่คุณจะพิมพ์มันออกมา ส่วนโอเวอร์โหลดตัวที่สองรับข้อมูล UTF-16 ชุดเดียวกันในรูปบัฟเฟอร์ PWORD ดิบพร้อมจำนวนหน่วยรหัสที่ระบุชัด ซึ่งเป็นรูปแบบที่ควรใช้เมื่อข้อความมาจาก API แทนที่จะมาจากสตริงของ Delphi บน Delphi รุ่นเก่าที่มาก่อนการเลือกโอเวอร์โหลดสำหรับชนิดเหล่านี้ รูปแบบที่รับสตริงถูกเปิดออกมาภายใต้ชื่อ RtLTextOutStr พร้อมรายการพารามิเตอร์ชุดเดียวกัน
การแบ่งหน้าที่ระหว่างการเรียกวาดข้อความสองตัวนี้เข้มงวด TextOut วาดโค้ดพอยต์ตามลำดับที่คุณส่งเข้าไป ซึ่งถูกต้องสำหรับอักษรละติน ซีริลลิก และ CJK แต่ผิดสำหรับภาษาอาหรับและฮีบรู ส่วน RtLTextOut เรียงลำดับแต่ละบรรทัดใหม่ให้เป็นลำดับเชิงสายตาแบบขวาไปซ้ายก่อน แล้วจึงวาด โดยรักษาคำละตินและตัวเลขที่แทรกอยู่ให้อ่านจากซ้ายไปขวาภายในบรรทัด HotPDF จงใจแยกสองเมธอดนี้ออกจากกันแทนที่จะเดาทิศทางจากตัวอักขระ การเลือกว่าจะเรียกตัวไหนจึงเป็นการเลือกว่าจะได้พฤติกรรมของระบบเขียนแบบใด ใช้ RtLTextOut สำหรับช่วงข้อความขวาไปซ้าย ใช้ TextOut สำหรับอย่างอื่นทั้งหมด และอย่าส่งอย่างหนึ่งผ่านอีกอย่างเด็ดขาด เหตุผลว่าทำไมการเรียงลำดับใหม่จึงมีอยู่ อัลกอริทึมทวิทิศทางของ Unicode และการเชื่อมตัวอักษรตามบริบทของภาษาอาหรับทำอะไรจริง ๆ และการจัดรูปอักษรของ HotPDF หยุดตรงไหน คือหัวข้อของบทความคู่กันว่าด้วยการจัดรูปข้อความอาหรับและ RTL ด้วย HotPDF ทุกอย่างด้านล่างคือการตั้งค่าเชิงปฏิบัติ

อาร์กิวเมนต์ charset เป็นตัวตัดสินระบบเขียน
สิ่งที่บอก RtLTextOut ว่ากำลังจัดวางภาษาอาหรับหรือฮีบรูไม่ใช่ตัวเมธอด แต่คือฟอนต์ SetFont รับค่า charset ของ Windows เป็นอาร์กิวเมนต์ตัวที่สี่ และค่านั้นพากฎของระบบเขียนเข้าไปในการเรียกแบบขวาไปซ้าย: 178 เลือกภาษาอาหรับ 177 เลือกภาษาฮีบรู ตั้ง charset แล้ววาด สองบรรทัดด้านล่างก็ออกมาในลำดับการอ่านที่ถูกต้องโดยไม่ต้องตั้งค่าอะไรเพิ่ม
// อาหรับ: charset 178 บอก RtLTextOut ให้ใช้กฎของภาษาอาหรับ
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// ฮีบรู: charset 177 สลับกฎไปเป็นภาษาฮีบรู
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
มีรายละเอียดเรื่องลำดับหนึ่งข้อที่พลาดได้ง่าย: SetFont ต้องมาก่อน และต้องเรียกซ้ำหลัง AddPage ทุกครั้ง เพราะฟอนต์ปัจจุบันซึ่งรวม charset ด้วยนั้นไม่รอดข้ามการขึ้นหน้าใหม่ ลืมเรียกซ้ำแล้วหน้าที่สองจะถอยไปใช้ฟอนต์อะไรก็ตามที่ยังทำงานอยู่ ซึ่งสำหรับภาษาอาหรับมักหมายถึงกล่องเปล่า
มันไม่ได้กลับด้านข้อความที่คุณกลับด้านไปแล้ว
ความผิดพลาดข้อเดียวที่กลืนเวลาไล่บั๊กไปมากที่สุดตรงนี้คือการป้อนสตริงที่คุณกลับด้านด้วยมือไปแล้วให้ RtLTextOut ผู้คนมาถึงเมธอดนี้หลังจากลองครั้งแรกด้วย TextOut ธรรมดาแล้วได้ผลกลับหลัง และทางแก้ขัดตาทัพที่พบบ่อยคือกลับลำดับตัวอักษรในโค้ดก่อนวาด RtLTextOut กลับด้านภายในตัวมันเองอยู่แล้ว สตริงที่กลับด้านมาก่อนจึงถูกกลับด้านอีกรอบแล้วลงเอยกลับที่เดิม ให้ส่งข้อความในลำดับตรรกะ ลำดับที่คุณจะพิมพ์และอ่านออกเสียง แล้วปล่อยให้การเรียกนั้นจัดลำดับใหม่เอง
กับดักนี้ร้ายกว่าการกลับด้านธรรมดา เพราะสตริงที่ถูกกลับด้านสองรอบอาจดูถูกต้องกับวลีทดสอบที่เป็นภาษาอาหรับล้วนหนึ่งวลี แล้วพังทันทีที่บรรทัดหนึ่งมีคำละตินหรือตัวเลขติดมา ภายในบรรทัดขวาไปซ้าย ช่วงข้อความที่แทรกอยู่เหล่านั้นควรอ่านจากซ้ายไปขวา และการกลับด้านด้วยมือทำลายการซ้อนชั้นนั้น ขณะที่กรณีอาหรับล้วนบังเอิญรอดมาได้ บั๊กจึงแล่นผ่านการทดสอบเบื้องต้นครั้งแรกของคุณ แล้วโผล่ทีหลังบนใบแจ้งหนี้จริงที่มีเลขบัญชีอยู่ในนั้น ให้ถอนการกลับด้านด้วยมือทุกจุดออกทันทีที่คุณเปลี่ยนมาใช้ RtLTextOut
ผลข้างเคียงเรื่อง Direction ที่ควรรู้ไว้
การเรียก RtLTextOut เปลี่ยนมากกว่าบรรทัดที่คุณกำลังวาด มันยังพลิกค่าความชอบเรื่องทิศทางการอ่านของเอกสารไปเป็นขวาไปซ้ายด้วย ซึ่งเป็นสิ่งเดียวกับที่คุณจะตั้งเองผ่านคุณสมบัติ Direction ตัวตั้งค่านั้นเพิ่ม vpDirection เข้าไปใน ViewerPreferences ของเอกสาร ซึ่งบอกโปรแกรมอ่านว่าจะจัดหน้าคู่อย่างไรและเลย์เอาต์แบบหน้าประกบเริ่มจากด้านไหน เมื่อทั้งเอกสารเป็นภาษาอาหรับหรือฮีบรู นี่คือสิ่งที่คุณต้องการพอดี และคุณได้มันมาฟรี
เรื่องนี้ควรรู้ไว้เพราะมันมองไม่เห็นบนหน้าเดียวโดยเฉพาะ ถ้าเอกสารเป็นซ้ายไปขวาเสียส่วนใหญ่แล้วมีบล็อกขวาไปซ้ายอยู่บล็อกเดียว การเรียก RtLTextOut ครั้งแรกก็ยังพลิกค่าความชอบของทั้งไฟล์อยู่ดี และไม่มีอะไรในหน้าตัวอย่างหน้าเดียวของคุณจะแสดงให้เห็น อาการจะโผล่หลายสัปดาห์ต่อมาเมื่อมีคนพิมพ์หนังสือเล่มเล็กแบบสองหน้า แล้วหน้าคู่ออกมากลับด้าน ถ้านั่นไม่ใช่สิ่งที่คุณต้องการ ให้ตั้ง Direction กลับอย่างชัดเจนหลังช่วงข้อความขวาไปซ้าย:
// RtLTextOut ตั้งทิศทางของเอกสารเป็น RightToLeft ไปแล้ว
// คืนค่าเป็นซ้ายไปขวาถ้าเอกสารเป็น LTR เป็นหลัก
Pdf.Direction := LeftToRight;
สำหรับเอกสารที่อ่านจากขวาไปซ้ายจริง ๆ ก็ปล่อยไว้อย่างนั้น ประเด็นคือให้รู้ว่าการเรียกนี้มีผลทั่วทั้งเอกสาร เพื่อไม่ให้เซอร์ไพรส์เรื่องหนังสือเล่มเล็กเกิดขึ้นเลย
ลงทะเบียนฟอนต์ที่คุณส่งไปด้วย ไม่ใช่ฟอนต์ที่คุณหวังว่าจะมีติดตั้งอยู่
การเรียงลำดับใหม่ทั้งหมดไม่มีความหมายเลยถ้าฟอนต์ไม่มีกลีฟให้วาด ความล้มเหลวคลาสสิกคือรายงานที่เรนเดอร์ออกมาสมบูรณ์แบบบนเครื่องนักพัฒนา ซึ่งบังเอิญมี Arial Unicode MS อยู่ แต่ออกมาเป็นแถวของกล่องเปล่าบนเซิร์ฟเวอร์ของลูกค้าที่ Windows แอบแทนที่ด้วยฟอนต์ซึ่งไม่รองรับภาษาอาหรับเลย ยาแก้คือเลิกเชื่อฟอนต์ของระบบที่ติดตั้งไว้ แล้วลงทะเบียนฟอนต์ที่คุณส่งไปพร้อมแอปพลิเคชัน
// ส่งฟอนต์อาหรับที่รู้จักไปด้วย แล้วลงทะเบียนก่อนวาด
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
มีขอบเขตสองข้อที่ติดมากับการลงทะเบียน ฟอนต์ที่นำเข้ามาผ่าน RegisterUnicodeTTF จะถูกฝังลงไป และการจัดการ Unicode แบบฝังของ HotPDF ต้องการเอกสารที่เป็น PDF 1.5 ขึ้นไป ข้อนี้กัดก็ต่อเมื่อมีบางอย่างปลายน้ำยืนกรานให้เป็น PDF 1.4 แต่เมื่อเกิดขึ้นแล้วความล้มเหลวจะเงียบ อีกข้อเป็นเรื่องกฎหมายมากกว่าเทคนิค: ไฟล์ TrueType มีบิตสิทธิ์การฝังติดมาด้วย แบบอักษรที่ดูดีบนจอก็อาจมีสัญญาอนุญาตที่ห้ามส่งมันไปในเอกสารของลูกค้า ตรวจสัญญาอนุญาตก่อนจะฝัง ไม่ใช่หลังจากมีคนร้องเรียน
ตัวอย่างโปรแกรมคอนโซลแบบครบชุด
เมื่อประกอบชิ้นส่วนทั้งหมดเข้าด้วยกัน นี่คือโปรแกรมที่เบ็ดเสร็จในตัวซึ่งเขียนหนึ่งหน้าที่มีบรรทัดภาษาอาหรับ บรรทัดภาษาฮีบรู และบรรทัดผสมที่มีชื่อผลิตภัณฑ์เป็นอักษรละติน แต่ละบล็อกตั้ง charset ของตัวเอง แล้วจึงวาดในลำดับตรรกะ
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // ยูนิตหลักของ HotPDF
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// หัวข้อภาษาละตินไปตามเส้นทาง TextOut ธรรมดา
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// อาหรับ: charset 178 ลำดับตรรกะ RtLTextOut จัดลำดับใหม่ให้
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// ฮีบรู: charset 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// บรรทัดผสม: คำละตินที่แทรกอยู่ยังอ่านจากซ้ายไปขวา
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 640, 0,
'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');
Pdf.EndDoc;
Writeln('Wrote RtLTextOut.pdf');
finally
Pdf.Free;
end;
end.
รันมันแล้วเปิดผลลัพธ์ดู บรรทัดภาษาอาหรับและฮีบรูอ่านจากขวาไปซ้าย ตัวอักษรเชื่อมกันในที่ที่ระบบเขียนกำหนดให้เชื่อม และในบรรทัดสุดท้าย คำ HotPDF วางตัวจากซ้ายไปขวาอยู่ภายในช่วงข้อความภาษาอาหรับ การซ้อนชั้นนั้นคือผลลัพธ์ทวิทิศทางที่ถูกต้อง ไม่ใช่บั๊ก แม้ผู้ตรวจครั้งแรกจะแจ้งว่าเป็นบั๊กอยู่เป็นประจำ บทความเรื่องการจัดรูปอักษรที่ลิงก์ไว้ข้างต้นอธิบายว่าทำไมกฎของ Unicode จึงเรียกร้องเช่นนั้น และจะเขียนเกณฑ์การยอมรับอย่างไรให้ไม่มีใครแจ้งเรื่องนี้อีก
ข้อผิดพลาดที่พบบ่อยและทางแก้
ความล้มเหลวทุกข้อด้านล่างเคยปรากฏในเธรดสนับสนุนจริง และแต่ละข้อสาวกลับไปหาหัวข้อใดหัวข้อหนึ่งข้างต้นได้
- ผลลัพธ์อ่านกลับหลังหรือสับสนบนบรรทัดผสม — สตริงถูกกลับด้านด้วยมือก่อนการเรียก มักเป็นทางแก้ที่ตกค้างจากการลองใช้
TextOutให้ลบการกลับด้านด้วยมือทุกจุดแล้วส่งลำดับตรรกะเข้าไปRtLTextOutกลับด้านภายในตัวมันเอง - ตัวอักษรพิมพ์ออกมาแยกกันในรูปเดี่ยว — ข้อความไปผ่าน
TextOutธรรมดา หรือSetFontถูกเรียกโดยไม่มี charset ของขวาไปซ้าย ให้วาดด้วยRtLTextOutแล้วส่ง 178 สำหรับภาษาอาหรับหรือ 177 สำหรับภาษาฮีบรูเป็นอาร์กิวเมนต์ตัวที่สี่ของSetFont - กล่องเปล่าบนเครื่องของลูกค้า — Windows แทนที่ด้วยฟอนต์ที่ไม่รองรับภาษาอาหรับหรือฮีบรู ให้เลิกเรียกชื่อฟอนต์ที่ติดตั้งไว้ในระบบ แล้วลงทะเบียนแบบอักษรที่คุณส่งไปเองผ่าน
RegisterUnicodeTTFและเรียกSetFontด้วยชื่อนั้น - หน้าที่สองเรนเดอร์ด้วยฟอนต์ผิด — ฟอนต์ปัจจุบันไม่รอดข้าม
AddPageให้เรียกSetFontซ้ำโดยรวม charset ด้วย หลังการขึ้นหน้าใหม่ทุกครั้ง - หน้าคู่แบบสองหน้าพิมพ์ออกมากลับด้านบนเอกสารที่เป็น LTR เป็นหลัก — การเรียก
RtLTextOutครั้งแรกพลิกค่าDirectionของเอกสารเป็นผลข้างเคียง ให้ตั้งPdf.Direction := LeftToRightหลังช่วงข้อความขวาไปซ้าย - ข้อความ Unicode ที่ฝังไว้เสื่อมคุณภาพอย่างเงียบ ๆ ที่ปลายน้ำ — มีบางอย่างในไปป์ไลน์บังคับให้เป็น PDF 1.4 ขณะที่การจัดการ Unicode แบบฝังของ HotPDF ต้องการ 1.5 ขึ้นไป ให้ยกระดับเวอร์ชันเอกสารหรือถอดข้อจำกัดที่ปลายน้ำออก
ก่อนส่งมอบรูปแบบไฟล์ ให้ตรวจสอบให้พ้นการมองด้วยตา: คัดลอกข้อความออกจากโปรแกรมอ่านกลับมา ลองค้นหาภายในเอกสาร เปิดไฟล์บนเครื่องที่ไม่มีฟอนต์สำหรับการพัฒนาของคุณ และวางเอกสารจริงหนึ่งฉบับต่อหน้าเจ้าของภาษา รายการตรวจสอบฉบับเต็ม แผนที่ความครอบคลุมรายระบบเขียน และคลังสตริงทดสอบที่ควรสร้างไว้ ล้วนอยู่ในบทความคู่กันว่าด้วยการจัดรูปข้อความอาหรับและ RTL ด้วย HotPDF
การเรียก RtLTextOut, SetFont และ RegisterUnicodeTTF ที่แสดงไว้ที่นี่เป็นส่วนหนึ่งของ HotPDF Delphi Component สำหรับ Delphi และ C++Builder