يفتح مستخدم كفيف تقريرًا ربع سنوي في عارض Delphi الجديد اللامع، ويقوم بتشغيل NVDA، ويسمع تذييل الصفحة، ثم عمودًا من الأرقام، ثم العنوان الذي سيقرأه أي قارئ مبصر أولاً. أو لا يسمع شيئًا على الإطلاق. تبدو الصفحة مثالية على الشاشة، وهذا هو الفخ بالضبط: التصيير والقراءة مشكلتان مختلفتان يتم حلهما بواسطة كود مختلف. الترتيب الذي يرسم به PDF صوره الرمزية ليس عليه أي التزام بمطابقة الترتيب الذي يجب أن يسمعه الشخص، لذا فإن العارض المبني فقط على استدعاءات التصيير ينتج صورة لا تشوبها شائبة وسردًا غير قابل للاستخدام. يحمل مكون PDFium، غلاف VCL/LCL حول محرك PDFium لـ Delphi و C++Builder و Lazarus، مجموعة منفصلة من واجهات برمجة تطبيقات (APIs) القراءة لهذا السبب. لا يمكن لـ API الرسم استعادة ترتيب قراءة لم تُعط له أبدًا
القارئ الذي يمكن الوصول إليه يقوم أو يسقط على ثلاثة أشياء. يجب عليه استخراج ترتيب يمكن لقارئ الشاشة التحدث به، وإبقاء مؤشر كلمة مرئي مثبتًا على ما يقوله الصوت، والاعتراف عندما لا يتم وسم المستند أبدًا بدلاً من التخمين والتظاهر. كل واحد لديه API واضحة للوصول إليها وفشل يعض إذا تخطيت التفاصيل
ترتيب القراءة يعيش في شجرة الهيكل، وليس ترتيب الرسم
يعرّف ISO 32000-1 §14.8 الهيكل المنطقي على أنه شجرة من العناصر المتراكبة فوق محتوى الصفحة. يذهب PDF/UA (ISO 14289-1) إلى أبعد من ذلك ويجعل تلك الشجرة إلزامية: يجب أن تكون كل قطعة من المحتوى الحقيقي قابلة للوصول من خلالها بترتيب القراءة، مع وضع علامة على القطع الأثرية (artifacts) للصفحة على هذا النحو وتخطيها. التقرير الموسوم بشكل صحيح يعرف أن "النتائج الربع سنوية" هو عنوان من المستوى الثاني وأن شبكة الإجماليات هي جدول يحتوي على خلايا رأس. التقرير غير الموسوم عبارة عن كومة من مجموعات الصور الرمزية المتمركزة والتي تصادف أنها تبدو كمستند
تسير ReadablePageContent في هذا الهيكل عندما يكون حاضرًا وتعيد أجزاء موسومة بـ Kind دلالي، قيم مثل cfHeading و cfParagraph، حتى تتمكن واجهة المستخدم من قول "عنوان" قبل الكلمات بدلاً من قراءة سطر غامق كنص أساسي عادي. مع عدم وجود شجرة قابلة للاستخدام، يتراجع نفس الاستدعاء إلى تحليل التخطيط الإرشادي (heuristic layout analysis): اكتشاف الأعمدة، وتجميع خطوط الأساس، والترتيب من اليسار إلى اليمين ومن أعلى إلى أسفل. هذا التراجع جيد لمذكرة بعمود واحد ومهتز لرسالة إخبارية، أو نموذج متعدد الأعمدة، أو أي شيء يحتوي على شريط جانبي أو اقتباس سحب. ما يهم هو معرفة النتيجة التي حصلت عليها، ويخبرك الـ API بذلك بصراحة. يحمل سجل TPdfReadableContent حقل Source تم تعيينه إلى rosStructure عندما يأتي الترتيب من الشجرة الموسومة، أو rosHeuristic عندما يتم استنتاجه من الهندسة. إظهار ترتيب مخمن كما لو تم التحقق منه يعني أنك شحنت نسخة إمكانية الوصول من شارة مرور على بناء لم يقم بتشغيله أحد
الخطوة الرخيصة في وقت الفتح هي قراءة IsTagged واستدعاء ValidatePdfUa مرة واحدة، ثم تخزين الإجابة مؤقتًا. فحص PDF/UA الفاشل ليس سببًا لرفض الملف. إنه سبب لوضع "ترتيب قراءة مقدر" في شريط الحالة، بحيث عندما يرسل عميل شكوى عبر البريد الإلكتروني حول سرد مشوه، يعرف الدعم بالفعل ما إذا كانوا ينظرون إلى مشكلة في الوسم في الملف أو خطأ في التعليمات البرمجية الخاصة بك
من الصفحة إلى قائمة انتظار الكلام باستخدام ReadingUnits
بالنسبة لتحويل النص إلى كلام (TTS)، تقوم ReadingUnits بالعمل الشاق. فهي تعيد مصفوفة من سجلات TPdfReadingUnit للصفحة النشطة، كل واحد يحمل النص الذي يجب التحدث به، ودوره الدلالي، والمستطيلات التي تحدد موقعه على الصفحة. يوجد رفيق على مستوى المستند، DocumentReadingUnits، عندما تريد قراءة مستمرة عبر الصفحات. تسقط وحدة واحدة مباشرة في فتحة واحدة من قائمة انتظار الكلام:
procedure TReaderForm.QueuePageSpeech(PageNumber: Integer);
var
Units: TPdfReadingUnits;
i: Integer;
begin
Pdf.PageNumber := PageNumber; // ReadingUnits works on the active page
Units := Pdf.ReadingUnits;
FSpeechQueue.Clear;
for i := Low(Units) to High(Units) do
FSpeechQueue.Add(Units[i]); // text + semantics + highlight rects
FCurrentPage := PageNumber;
SpeakNextUnit;
end;
من السهل أن نخطئ في شيئين في هذه الحلقة. احتفظ بقائمة الانتظار لكل صفحة وقم بإعادة بنائها كلما تنقل المستخدم، لأن وحدات القراءة تحمل مستطيلات مساحة الصفحة؛ قائمة الانتظار المتبقية من الصفحة الثالثة سترسم تمييزاتها على الصفحة الرابعة. وتعامل مع مصفوفة Units فارغة على صفحة تحتوي بوضوح على محتوى ككاشف لك للصور فقط. الصفحة الممسوحة ضوئيًا هي بكسلات بدون طبقة نص تحتها، والإجابة الصحيحة هي التحدث بتحذير ("هذه الصفحة لا تحتوي على نص قابل للاستخراج") بدلاً من الصمت بطريقة لا يستطيع المستمع تمييزها عن التعليق
مؤشر كلمة يتبع الصوت
يبدو تمييز فقرة كاملة في وقت واحد بطيئًا لمستخدم ضعيف البصر يتتبع الكلمات بالعين أثناء قراءتها بصوت عالٍ. التمييز على مستوى الكلمة، تأثير الكاريوكي، يحتاج إلى قطعتين: هندسة كل كلمة، وطريقة لتعيين تقارير تقدم محرك TTS على تلك الهندسة. تمنحك PageWordBoxes الهندسة في سجلات TPdfWordBox، كل منها يحتوي على نص الكلمة، وإزاحة الحرف الخاص بها، وعدد أحرفها، ومستطيل مساحة الصفحة. تمنحك TrackReadingWordAt التعيين. قم بتزويدها بموضع الحرف الذي يبلغ عنه حدث حدود الكلمة الخاص بـ SAPI بالفعل، وسوف تحل هذه الإزاحة إلى فهرس في مصفوفة مربع الكلمة وترسم المؤشر على الكلمة المطابقة في استدعاء واحد
procedure TReaderForm.PrepareKaraoke(PageNumber: Integer);
begin
// The view's word boxes come from the page the view displays.
// Setting Pdf.PageNumber alone would not move the view
PdfView.PageNumber := PageNumber;
FWordBoxes := PdfView.PageWordBoxes;
end;
procedure TReaderForm.OnTtsWordBoundary(Sender: TObject; CharIndex: Integer);
var
WordIdx: Integer;
begin
// TrackReadingWordAt maps the offset AND paints the word cursor
WordIdx := PdfView.TrackReadingWordAt(FCurrentPage, CharIndex);
if WordIdx < 0 then
PdfView.ClearReadingWord; // boundary ran past the page text
end;
العقد سخي من ناحية ولا يرحم من ناحية أخرى. الجزء السخي: يحتفظ TrackReadingWordAt بذاكرة التخزين المؤقت لمربع الكلمات الخاصة به للصفحة التي يتتبعها، لذلك لا يوجد شيء ليتم تحميله مسبقًا، ولا يحدث أي تصيير على الإطلاق لأن مربعات الكلمات تأتي من طبقة النص. يمكن لخدمة كلام بدون رأس وليس لها نافذة مرئية الاستمرار في تتبع المواضع. الجزء الذي لا يرحم: يجب أن يشير فهرس الحرف إلى النص الذي استخرجه المكون، وليس إلى بعض السلاسل المنظفة التي أنشأتها بنفسك. عندما يتجاوز CharIndex نهاية نص الصفحة، تُرجع الدالة -1 بدلاً من إثارة استثناء، وهو ما يحدث طوال الوقت عندما يطلق محرك TTS حدث حد أخير لعلامات الترقيم الزائدة. اقرأ -1 على أنه "امسح المؤشر"، وليس كخطأ أبدًا
على جانب العرض، يقوم ReadingWordColor بتعيين لون المؤشر. يصمد لون العنبر الافتراضي على معظم خلفيات الصفحات، لكن اختبره تحت كل مرشح عرض يقدمه عارضك. يمكن أن يتلاشى مؤشر العنبر تمامًا تحت انعكاس اللون، والانعكاس الذي يعمل جنبًا إلى جنب مع الكلام هو بالضبط كيفية عمل المستخدم ضعيف البصر، لذا فإن المجموعة الوحيدة التي تحتاج أكثر من غيرها للوصول إليها بشكل صحيح هي المجموعة التي لا يمارسها العرض التوضيحي السريع أبدًا. قم بتعيين ReadingWordFollow إلى True وسيقوم العرض بتمرير الكلمة المنطوقة إلى مرمى البصر بمفردها، وهو ما لا يمكنك الاستغناء عنه في صفحة مكبرة تمتد عبر الشاشات. ضع في اعتبارك قاعدة نطاق واحدة: يقوم SetReadingWord بالطلاء فقط على صفحة TPdfView النشطة. حدد مقدمًا ما إذا كان التمرير اليدوي يوقف الكلام مؤقتًا أو ما إذا كان سلوك المتابعة يتجاوزه، لأن عدم اختيار أي منهما يترك الصوت يقرأ بينما يستقر المؤشر في مكان ما خارج الشاشة
المستندات التي تكسر القارئ الخاص بك
حفنة من أشكال الإدخال تهزم تنفيذًا ساذجًا بشكل موثوق بما يكفي لتنتمي كعينات دائمة في مجموعة الانحدار (regression suite)، وليس كأخطاء لمرة واحدة تقوم بإصلاحها ونسيانها
- ملفات غير موسومة ولكن غنية بالنص. يميل الترتيب الإرشادي إلى أن يكون صحيحًا لتقرير خطي وخاطئًا في اللحظة التي يدخل فيها شريط جانبي أو اقتباس سحب. ضع علامة على الترتيب كمقدر، سواء في واجهة المستخدم أو في سجل التشخيص الخاص بك، بحيث يمكن قراءة الفشل لاحقًا
- عمليات المسح للصور فقط. لا توجد طبقة نص على الإطلاق. التقطها من خلال وحدات قراءة فارغة ووجه المستخدم إلى خطوة التعرف الضوئي على الحروف (OCR) في المنبع بدلاً من ترك القارئ يروي صفحة فارغة
- دمج الأحرف والنصوص المختلطة. لا تنهار علامات دمج Unicode دائمًا من واحد إلى واحد إلى كلمات مرئية، لذا يمكن أن ينحرف عدد مربعات الكلمات عما يتوقعه المحلل اللغوي الخاص بك (tokenizer). لا تقم بفهرسة مصفوفة مربع الكلمة باستخدام الإزاحات التي حسبتها عن طريق تقسيم النص بنفسك؛ استخدم فقط الفهارس التي ترجعها
TrackReadingWordAt
اختبره كمدقق، وليس كعرض توضيحي
"لقد قرأ نموذجتي بصوت عالٍ" لا يثبت شيئًا. التمريرة التي يمكنك الدفاع عنها تقوم بتشغيل ثلاثة ملفات من خلال البناء النهائي مع إرفاق NVDA: ملف واحد موسوم ومعروف، حيث يتم الإعلان عن العناوين كعناوين ويتم قراءة الجدول بترتيب الصفوف؛ ملف واحد غير موسوم ومعروف، حيث يكون مؤشر الترتيب المقدر مرئيًا؛ وعملية مسح، حيث يتم التحدث فعليًا بالتحذير من عدم وجود نص. يمارس كل واحد مسارًا تتخطاه الحالة السعيدة
من هناك، تأكد من بقاء مؤشر الكلمة مقفلاً بمعدل كلام مزدوج وفي النصف، وأن تمرير ReadingWordFollow لا يصارع مع تمرير المستخدم الخاص. ثم قم بتشغيل الكلام أثناء التمرير عبر كل مرشح ألوان وراقب ألا يختفي المؤشر أبدًا. يغطي مقال مرشح ألوان ضعف الرؤية مسار التصيير هذا بالتفصيل، وتقوم الغوص العميق في مؤشر كلمة الكلام بتفكيك توقيت TTS
تُشحن API الخاصة بوحدة القراءة ومربع الكلمة المستخدمة أعلاه مع مكون PDFium لـ Delphi و C++Builder (VCL) و Lazarus/FPC (LCL). تربط صفحة المنتج مرجع API الكامل، بما في ذلك تخطيطات السجل لوحدات القراءة ومربعات الكلمات وراء هذه الأمثلة