อุปสรรคหลักในการสร้างโปรแกรมอ่าน PDF ที่เปิดใช้งาน TTS คือการแสดงข้อความภายใน PDF DOM จะไม่จับคู่แบบ 1:1 กับข้อความที่แสดงบนหน้าจอหรือข้อความที่พูดโดยเอนจิน TTS โดยอัตโนมัติ ใน PDFium การอ่านข้อความผ่าน FPDFTextObj_GetText จะให้ส่วนย่อยของเนื้อหา แต่ส่วนย่อยนี้ไม่ได้นำพาดัชนีอักขระเชิงเส้นที่จำเป็นในการเน้นข้อความบนหน้าที่แสดงผลผ่าน FPDFText_* APIs มาด้วย
เนื่องจากช่องว่างและการจัดลำดับข้อความอาจแตกต่างกันอย่างมากระหว่างโครงสร้าง DOM และเลย์เอาต์ทางภาพ การพยายามจับคู่ส่วนย่อยการอ่านโดยตรงกับดัชนีอักขระของหน้าจึงมักจะล้มเหลว
การใช้ Word Boxes เป็นสะพานเชื่อมตัวกลาง
วิธีการแก้ปัญหาทางสถาปัตยกรรมคือการใช้ "Word Boxes" (เช่น TPdfWordBox) เป็นหน่วยพื้นฐานของการโต้ตอบกับข้อความ Word Box จะเชื่อมโยงพิกัดเรขาคณิตของคำบนหน้า ดัชนีอักขระที่แสดงถึงคำนั้น และตัวเพย์โหลดสตริง (string payload) เองเข้าด้วยกันโดยเนื้อแท้
เมื่อสร้างเพย์โหลดข้อความที่จะพูดสำหรับเอนจิน TTS คุณจะประกอบมันขึ้นจาก Word Boxes ตามลำดับเหล่านี้แทนที่จะเป็นส่วนย่อยของเนื้อหาดิบ สิ่งนี้ช่วยให้คุณสามารถรักษา "word cursor" ที่ใช้งานอยู่ได้ ในขณะที่เอนจิน TTS พูด มันจะส่งสัญญาณดัชนีคำปัจจุบัน เนื่องจากดัชนีนั้นสอดคล้องโดยตรงกับ Word Box ที่เฉพาะเจาะจง คุณจึงสามารถเน้นข้อความบนหน้าจอได้ทันทีโดยใช้ดัชนีอักขระที่เก็บไว้ภายในกล่องนั้น ทำให้เกิดการซิงโครไนซ์ที่สมบูรณ์แบบระหว่างเสียงและการเน้นข้อความทางภาพโดยไม่ต้องใช้การจับคู่สตริงแบบคลุมเครือ (fuzzy string matching)
การแยกเซสชันการอ่านออกจาก Host UI
ข้อผิดพลาดทั่วไปเมื่อใช้ TTS คือการเชื่อมโยงเอนจินเสียงพูด (speech engine) ตัวจับเวลาการทำงาน (ticking timer) และมุมมอง PDF เข้าด้วยกันอย่างแน่นหนา สิ่งนี้ทำให้การทดสอบแบบ headless เป็นไปไม่ได้ และล็อกตรรกะไว้กับ OS หรือเฟรมเวิร์ก UI ที่เฉพาะเจาะจง (เช่น ผูกติดกับ Windows SAPI แต่เพียงผู้เดียว)
แต่ควรออกแบบ TPdfReadingSession ให้เป็น state machine ที่เป็นอิสระแทน:
- ไม่มีตัวจับเวลาภายใน: เวลาควรถูกส่ง (injected) เข้าไปโดยแอปพลิเคชันโฮสต์ผ่านเมธอด
Tick(SpeechStillBusy, ElapsedMs) - ไม่มีการพึ่งพาเอนจินโดยตรง: เซสชันไม่ควรลิงก์โดยตรงกับ TTS API การโต้ตอบทั้งหมดควรส่งออกไปภายนอกผ่านอีเวนต์ เช่น
OnWordChangeและOnChunkChange - Idempotent Event Guards: เนื่องจาก UI โฮสต์จะวนลูปและสั่งการ (tick) เซสชันบ่อยครั้ง เซสชันจึงต้องแน่ใจว่าจะไม่เรียกใช้อีเวนต์
OnWordChangeสำหรับคำเดิมเป็นครั้งที่สอง เพื่อป้องกันไม่ให้การเน้น UI กะพริบโดยไม่จำเป็น
ความรับผิดชอบของ Host
ด้วยการรักษาเซสชันให้เป็นตรรกะล้วนๆ แอปพลิเคชันโฮสต์ (Delphi Form หรือหน้าต่าง FPC/Lazarus ของคุณ) จะยังคงรับผิดชอบในการทำงานหนักๆ:
- เลื่อนไปยังหน้าถัดไปเมื่อการอ่านข้อมูลส่วนหนึ่ง (chunk) เสร็จสิ้น
- การประเมินจังหวะการอ่าน (เช่น การเน้นคำทุกๆ ~340ms หากเอนจิน TTS ขาดการเรียกกลับ (callbacks) ที่ขอบเขตคำที่แน่นอน เช่น NVDA controller client)
- การผลักดันสตริงจริงไปยัง COM หรือ native speech APIs
การปฏิบัติตามสถาปัตยกรรมที่แยกออกจากกันนี้ จะทำให้แอปพลิเคชัน Delphi ของคุณสามารถมอบประสบการณ์การอ่านเอกสารระดับโลกที่เข้าถึงได้ ซึ่งยังคงทำงานได้รวดเร็ว ปลอดภัยต่อหน่วยความจำ และตอบสนองได้อย่างเหลือเชื่อ
หมายเหตุ: TPdfReadingSession, TPdfWordBox และคุณลักษณะสะพานการเข้าถึงถูกสร้างขึ้นโดยตรงใน PDFium VCL Component