مقاله فنی

برجسته‌سازی کلمه به کلمه TTS در بیننده‌های PDFium دلفی

یک ویژگی بلندخوانی فراتر از صدا، یک وظیفه قابل مشاهده دارد: وقتی هر کلمه گفته می‌شود، باید آن کلمه را در صفحه روشن کند و آن را در دید نگه دارد. برای انجام این کار به کادر محدودکننده (bounding box) هر کلمه نیاز دارید که در همان جریان کاراکتری که موتور گفتار از روی آن می‌خواند نمایه شده باشد. اگر کادرها را دریافت کنید اما نمایه‌سازی را از دست بدهید، برجسته‌سازی یک یا دو کلمه از صدا عقب می‌افتد؛ اگر نمایه‌سازی را دریافت کنید اما وضعیت صفحه را به اشتباه مدیریت کنید، برجسته‌سازی کلاً روی صفحه اشتباهی فرود می‌آید. بخش گفتاری این، یعنی خود ترکیب‌کننده (synthesizer)، بخشی است که به ندرت می‌شکند. SAPI مرزهای کلمه را تا کاراکتر گزارش می‌دهد. چیزی که می‌شکند لایه نگاشت نازک بین افست (offset) کاراکتر در بافر گفتار و یک مستطیل در صفحه رندر شده است

کامپوننت PDFium آن نگاشت را برای Delphi، C++Builder و Lazarus ارائه می‌دهد، به طوری که کادرهای کلمه از نسخه 1.53 و مکان‌نمای ردیابی از نسخه 1.56 در دسترس هستند. این سطح عمداً باریک است: فراخوانی که کادرهای کلمه را برای یک صفحه برمی‌گرداند، ردیابی که افست کاراکتر را به یک برجسته‌سازی رنگ‌آمیزی شده تبدیل می‌کند، و دو ویژگی برای رنگ و اسکرول خودکار. هرچند که این باریک است، اما ترتیبی که در آن چیزها را فراخوانی می‌کنید تعیین می‌کند که آیا ویژگی کار می‌کند یا خیر، و بیشتر شکست‌های زیر ناشی از فراخوانی توابع درست به ترتیب اشتباه است

کاراکترها کلمه نیستند، و موتورهای TTS به کاراکترها صحبت می‌کنند

یک موتور گفتار یک رشته مسطح را مصرف می‌کند و پیشرفت را به عنوان موقعیت‌های کاراکتر درون آن رشته گزارش می‌دهد. یک صفحه PDF گلیف‌هایی (glyphs) دارد که در فضای صفحه قرار گرفته‌اند، جایی که یک "کلمه" یک خوشه اکتشافی از اجراهای گلیف است. این دو سیستم مختصات هیچ اشتراکی ندارند مگر اینکه متنی که به ترکیب‌کننده می‌دهید بایت به بایت همان متنی باشد که کادرهای کلمه از آن محاسبه شده‌اند. این قانون یک است و بخشش‌ناپذیر است. نرمال‌سازی فضای سفید، حذف خط تیره‌های نرم یا «پاک‌سازی» متن استخراج شده قبل از صحبت کردن آن، و هر افست پایین‌دست در سکوت اشتباه است. دقیقاً همان چیزی را که استخراج کرده‌اید بیان کنید، یا یک جدول بازنگاشت صریح برای افست نگه دارید. گزینه سومی وجود ندارد که از اسناد واقعی جان سالم به در ببرد

جدول بازنگاشت یک مورد حاشیه‌ای فرضی نیست. لحظه‌ای که رابط کاربری شما یک اعلامیه صفحه گفتاری ("صفحه پنج") وارد می‌کند یا یک مخفف را برای ترکیب‌کننده گسترش می‌دهد، رشته گفتاری از رشته استخراج شده منحرف می‌شود. موقعیت و طول هر درج را ثبت کنید، سپس قبل از هر فراخوانی ردیابی، تنظیم انباشته شده را کم کنید. این شاید بیست خط حسابداری باشد و تفاوت بین برجسته‌سازی است که از درخواست ویژگی بعدی جان سالم به در می‌برد و ویژگی‌ای است که اولین باری که شخصی درخواست سرفصل‌های گفتاری می‌کند، می‌شکند

آنچه کادر کلمه به شما می‌دهد

هر رکورد TPdfWordBox شامل متن کلمه، فیلد StartIndex و Count کاراکتر در متن صفحه، یک Rect فضای صفحه و شماره Page مبتنی بر 1 است. فیلد StartIndex پلی بین دو سیستم مختصات است: همان افستی است که SAPI هنگام خواندن برمی‌گرداند. PageWordBoxes آرایه کامل را برای صفحه فعال برمی‌گرداند:

procedure TReaderForm.PreparePage(PageNo: Integer);
begin
  PdfView.PageNumber := PageNo;   // the view's word boxes track its displayed page

  FWords := PdfView.PageWordBoxes;
  FPageText := BuildSpeechText(FWords);   // concatenate Word.Text in order

  if Length(FWords) = 0 then
    HandleImageOnlyPage(PageNo);          // a scan with no text layer
end;

نظر ترتیب‌بندی در اینجا باربر است. PageWordBoxes بیننده، لایه متنی صفحه‌ای را که بیننده در حال حاضر نمایش می‌دهد، توکن‌بندی می‌کند، بنابراین ابتدا نما را پیمایش کنید و سپس استخراج کنید؛ هیچ رندری مورد نیاز نیست، فقط یک سند باز است. (کامپوننت سند، TPdf، PageWordBoxes خاص خود را دارد که برای استفاده هدلس با Pdf.PageNumber کلید خورده است. این دو شماره صفحه مستقل هستند، که خود یک تله است.) نتیجه خالی در صفحه‌ای که محتوا را به وضوح نشان می‌دهد به معنای اسکن فقط تصویر است. آن را به سمت OCR هدایت کنید، یا حداقل آن را اعلام کنید ("صفحه ۴ حاوی هیچ متن قابل خواندنی نیست")، به جای اینکه بگذارید صدا بدون هیچ توضیحی ساکت شود

اتصال مرزهای کلمه SAPI به ردیاب

TrackReadingWordAt، در بیننده، لولای کل ویژگی است. یک شماره صفحه و یک شاخص کاراکتر به آن بدهید؛ کادر کلمه حاوی آن کاراکتر را پیدا می‌کند، مکان‌نمای خواندن را روی آن رنگ می‌کند، و شاخص کلمه، یا -1 را در زمانی که شاخص بین کلمات می‌افتد، برمی‌گرداند. اعلان مرز کلمه SAPI دقیقاً همان موقعیت کاراکتر مورد نظر را فراهم می‌کند:

procedure TReaderForm.OnSpeechWordBoundary(StreamPos: Integer);
var
  WordIdx: Integer;
begin
  // Maps the offset to a word box and moves the highlight in one call
  WordIdx := PdfView.TrackReadingWordAt(FPageNo, StreamPos);
  if WordIdx < 0 then
    Exit;                     // boundary fell outside any word: keep last highlight
end;

دو جزئیات دفاعی در اینجا حق خود را به دست می‌آورند. اول، TrackReadingWordAt حافظه پنهان کادر کلمه خود را برای صفحه ردیابی شده نگه می‌دارد، که با تغییر صفحه به طور خودکار بازسازی می‌شود، بنابراین هزینه هر مرز مهم نیست که مرزها با چه سرعتی برسند، ثابت می‌ماند. دوم، محدوده‌ها را سخاوتمندانه بررسی نمی‌کند. یک شاخص در یا فراتر از تعداد کاراکترهای صفحه به جای چسبیدن به کلمه نهایی، -1 را برمی‌گرداند. -1 را به‌عنوان «برجسته‌سازی قبلی را نگه دار» در نظر بگیرید، هرگز به‌عنوان خطا، زیرا اجراهای نقطه‌گذاری و فاصله خالی بین کلمه‌ها به طور مشروع مرزهایی تولید می‌کنند که متعلق به هیچ کلمه‌ای نیستند. ثبت هر -1 شما را دفن خواهد کرد. در عوض آن‌ها را در هر صفحه بشمارید و به هر صفحه‌ای که نسبت در آن بالا می‌رود سخت نگاه کنید، زیرا این معمولاً به معنای عدم تطابق نرمال‌سازی متن در قانون یک است

خود مکان‌نما: رنگ، دنبال کردن و پاک‌سازی

وقتی کادر کلمه را خودتان نگه می‌دارید، SetReadingWord مستقیماً برجسته‌سازی را رنگ‌آمیزی می‌کند، ReadingWordColor به آن استایل می‌دهد و ReadingWordFollow := True نما را دقیقاً به اندازه‌ای پیمایش می‌کند که کلمه گفته شده در دید بماند. این آخرین ویژگی جایگاه خود را به دست می‌آورد. اسکرول دست‌ساز "وسط‌چین کردن کلمه جاری" باعث می‌شود صفحه در هر خط‌شکنی دچار لرزش شود، و خوانندگانی که به حرکت حساس هستند، کل ویژگی را در عرض یک دقیقه خاموش خواهند کرد. برجسته‌سازی تنها در صفحه‌ای که در حال حاضر در TPdfView فعال نشان داده شده است، رندر می‌شود، بنابراین خواندن چند صفحه‌ای باید PageNumber را همگام با گفتار پیش ببرد، و سپس مرحله آماده‌سازی صفحه جدید را قبل از فرود اولین رویداد مرزی آن دوباره اجرا کند. اگر این را نادیده بگیرید، چند برجسته‌سازی اول در هر صفحه به مختصات کهنه اشاره خواهند کرد

procedure TReaderForm.StopReading;
begin
  FVoice.Stop;                // halt SAPI playback first
  PdfView.ClearReadingWord;   // then remove the highlight; a stale cursor reads as a bug
end;

تقارن در هنگام خاموش شدن چیزی است که برجسته‌سازی را صادق نگه می‌دارد. هر مکث، توقف و مسیر ورق زدن باید به ClearReadingWord ختم شود. آن را کنار بگذارید تا یک مستطیل کهربایی روی یک صفحه متوقف شده بنشیند که دقیقاً شبیه یک نقص است، این همان چیزی است که هر آزمایش‌کننده‌ای در پرونده ثبت می‌کند، حتی اگر هیچ‌چیز واقعاً نشکسته باشد

سرعت گفتار بیشتر از اندازه سند، این خط لوله را تحت فشار قرار می‌دهد. در ۳۰۰ کلمه در دقیقه، رویدادهای مرزی هر ۲۰۰ میلی‌ثانیه از راه می‌رسند، و در سریع‌ترین نرخ‌های SAPI آن‌ها سریع‌تر از آنچه چشم به راحتی ردیابی می‌کند می‌آیند. پاسخ درست به جای صف کردن، ائتلاف است. اگر مرز جدیدی در حالی رسید که به‌روزرسانی برجسته‌سازی همچنان در انتظار است، نسخه کهنه را کنار بگذارید و جدیدترین را رنگ کنید. مکان‌نمایی که هر کلمه را به ترتیب بازدید می‌کند اما نیم ثانیه تاخیر دارد احساس خرابی می‌دهد؛ مکان‌نمایی که در حالی که با صدا همگام است گاهی یک کلمه را رد می‌کند، این احساس را ندارد

موارد حاشیه‌ای که نسخه‌های نمایشی را از محصولات متمایز می‌کنند

چند دسته از اسناد درزها را در معرض دید قرار می‌دهند. ترکیب کاراکترها ظریف‌ترین آنها هستند: توالی‌های یونیکد مانند حرف پایه به‌علاوه یک حرکت‌گذاری (diacritic) ترکیبی می‌توانند شاخص‌های کاراکتر بیشتری نسبت به آنچه کلمه بصری نشان می‌دهد اشغال کنند، بنابراین هرگونه حساب و کتاب افست که فرض می‌کند هر گلیف یک شاخص دارد به آرامی منحرف می‌شود. این قوی‌ترین استدلال برای این است که اجازه دهید TrackReadingWordAt مالک نگاشت باشد به جای اینکه شماره کلمات را به صورت دستی محاسبه کنید. خط تیره‌گذاری پیش‌پاافتاده‌تر اما رایج‌تر است: کلمه‌ای که روی یک خط‌شکنی شکسته شده است تبدیل به دو کادر می‌شود و اگر آن را به عنوان یک توکن واحد بیان کنید، رویداد مرزی برای نیمه دوم آن به کادر اول حل می‌شود. این معمولاً خوب است، اما یک تصمیم است، بنابراین آن را عمداً به جای کشف آن اتخاذ کنید. تگ‌گذاری خود ترتیب خواندن را تغییر می‌دهد. هنگامی که یک سند دارای تگ‌های ساختار مناسب است (قلمرو ISO 14289, PDF/UA)، ترتیب کلمات از ساختار منطقی پیروی می‌کند؛ بدون آنها به هنجارهای اکتشافی طرح‌بندی برمی‌گردد و یک صفحه بدون تگ دو ستونی می‌تواند مستقیماً از روی هر دو ستون خوانده شود. صفحات چرخانده شده آخرین مورد رایج هستند: Rect هر کلمه هنوز آن را به درستی در فضای صفحه محدود می‌کند، اما یک خط‌مشی دنباله‌روی نما که برای جریان افقی تنظیم شده است، هنگامی که متن به صورت عمودی اجرا می‌شود به طرز ناهنجاری پیمایش می‌شود، بنابراین حداقل یک سند چرخانده شده را در مجموعه رگرسیون نگه دارید. برای رسیدگی به ترتیب خواندن، واحدهای سطح جمله از طریق ReadingUnits و پشته کمکی گسترده‌تر، به ساخت یک خواننده PDF قابل دسترسی در دلفی مراجعه کنید

یک محدودیت پلتفرم شکل استقرار را تعیین می‌کند. SAPI فقط برای ویندوز است. API کادر کلمه و ردیابی تحت Lazarus و FPC بایت به بایت یکسان است، اما بیلد‌های لینوکس و macOS به ترکیب‌کننده متفاوتی در پشت همان رویدادهای مرزی نیاز دارند؛ این راه‌اندازی در اجرای بیننده تحت Lazarus و FPC پوشش داده شده است. هزینه برجسته‌سازی نیز با حافظه پنهان صفحه شما در زمان افزایش سرعت گفتار تعامل دارد و محاسبات بودجه در رندرینگ حافظه پنهان و عملکرد زوم بدون تغییر به اینجا منتقل می‌شود

زمانی که برجسته‌سازی کلمه به کلمه، دانه‌بندی اشتباهی است

کارائوکه در سطح کلمه همیشه چیزی نیست که خواننده می‌خواهد. در نرخ‌های گفتار بالا، سوسو زدن مکان‌نما به صورت کلمه به کلمه به نویز بصری خودش تبدیل می‌شود و برخی از شنوندگان یک جمله را راحت‌تر از نور افکندن چشمک‌زن روی کلمات تکی دنبال می‌کنند. برای این مورد کامپوننت یک واحد درشت‌تر را در معرض نمایش قرار می‌دهد. ReadingUnits واحدهای سطح جمله و بلوک را برمی‌گرداند که هر کدام مستطیل‌های برجسته خود را دارند و به جای SetReadingWord آنها را با SetReadingHighlight رنگ می‌کنید. سیم‌کشی همان شکل را دارد: یک افست مرزی هنوز تعیین می‌کند که کدام واحد روشن شود، اما واحدی که برجسته می‌کنید به جای یک توکن واحد، یک بند یا یک خط را در بر می‌گیرد. خوانندگان کُندتر و پخش با نرخ بالا هر دو تمایل دارند آن را ترجیح دهند، و هیچ چیزی شما را از ارائه هر دو حالت در پشت یک تنظیم باز نمی‌دارد

کف نسخه‌ها ارزش آن را دارد که قبل از ساختن بر روی این موارد پین کنید: کادرهای کلمه به کامپوننت PDFium نسخه 1.53 یا بالاتر نیاز دارند و مکان‌نمای ردیابی به نسخه 1.56 نیاز دارد. API کامل خواندن، واحدهای سطح جمله و یک پیش‌نمایش تستی خواندن با صدای بلند کارآمد در صفحه محصول برای کامپوننت PDFium قرار دارد