Bài viết kỹ thuật

Tô Sáng Từng Từ TTS trong Trình Xem PDFium Delphi

Tính năng đọc to có một công việc hiển thị ngoài phần giọng đọc: khi mỗi từ được phát âm, nó phải làm sáng từ đó trên trang và giữ nó trong tầm nhìn. Để làm được điều đó, bạn cần hộp giới hạn của mỗi từ, được lập chỉ mục theo cùng luồng ký tự mà công cụ tổng hợp giọng nói đang đọc. Có được các hộp nhưng thiếu chỉ mục và vùng tô sáng sẽ trễ một hoặc hai từ so với âm thanh; có chỉ mục nhưng xử lý sai trạng thái trang và vùng tô sáng sẽ rơi vào sai trang hoàn toàn. Phần tổng hợp giọng nói, tức bộ tổng hợp, là phần hiếm khi bị hỏng. SAPI báo cáo ranh giới từ theo từng ký tự. Phần bị hỏng là lớp ánh xạ mỏng giữa độ lệch ký tự trong bộ đệm giọng nói và hình chữ nhật trên trang đã render

PDFium Component cung cấp ánh xạ đó cho Delphi, C++Builder và Lazarus, với word box có từ v1.53 và con trỏ theo dõi từ v1.56. Giao diện được thiết kế hẹp có chủ đích: một lệnh gọi trả về word box cho một trang, một bộ theo dõi chuyển độ lệch ký tự thành vùng tô sáng được vẽ, và một vài thuộc tính cho màu sắc và tự động cuộn. Dù hẹp như vậy, thứ tự bạn gọi các hàm quyết định tính năng có hoạt động hay không, và hầu hết các lỗi dưới đây đều do gọi đúng hàm theo sai trình tự

Ký tự không phải từ, và công cụ TTS đọc theo ký tự

Một công cụ tổng hợp giọng nói nhận vào một chuỗi phẳng và báo cáo tiến độ theo vị trí ký tự trong chuỗi đó. Một trang PDF có các glyph được đặt trong không gian trang, trong đó "từ" là một cụm heuristic của các nhóm glyph. Hai hệ tọa độ không có gì chung trừ khi văn bản bạn đưa cho bộ tổng hợp khớp byte-for-byte với văn bản mà word box được tính từ đó. Đó là quy tắc một, và nó không khoan nhượng. Chuẩn hóa khoảng trắng, loại bỏ dấu gạch nối mềm, hoặc "dọn dẹp" văn bản được trích xuất trước khi phát âm, và mọi độ lệch xuôi dòng đều sai một cách âm thầm. Phát âm chính xác những gì bạn trích xuất, hoặc giữ một bảng ánh xạ độ lệch tường minh. Không có lựa chọn thứ ba nào tồn tại được với tài liệu thực

Bảng ánh xạ lại không phải là trường hợp biên giả định. Ngay khi giao diện của bạn chèn thông báo trang được đọc ("trang năm") hoặc mở rộng một từ viết tắt cho bộ tổng hợp, chuỗi được phát âm sẽ khác với chuỗi được trích xuất. Ghi lại vị trí và độ dài của mỗi lần chèn, sau đó trừ đi lượng điều chỉnh tích lũy trước mỗi lần gọi theo dõi. Đó là khoảng hai mươi dòng ghi chú, và đó là sự khác biệt giữa một vùng tô sáng tồn tại qua lần cập nhật tính năng tiếp theo và một vùng tô sáng bị hỏng ngay lần đầu ai đó yêu cầu đọc tiêu đề

Word box cung cấp gì cho bạn

Mỗi bản ghi TPdfWordBox chứa văn bản của từ, StartIndex và số lượng ký tự Count trong văn bản trang, một Rect trong không gian trang và số Page theo cơ sở 1. Trường StartIndex là cầu nối giữa hai hệ tọa độ: đó là cùng độ lệch mà SAPI sẽ trả lại khi đọc. PageWordBoxes trả về toàn bộ mảng cho trang đang hoạt động:

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;

Chú thích về thứ tự là quan trọng. PageWordBoxes của viewer tokenize lớp văn bản của trang mà view hiện đang hiển thị, vì vậy hãy điều hướng view trước và trích xuất sau; không cần render, chỉ cần tài liệu đã mở. (Component tài liệu TPdf cung cấp PageWordBoxes riêng của nó được gắn với Pdf.PageNumber để dùng headless. Hai số trang là độc lập với nhau, đây cũng là một cái bẫy riêng.) Kết quả rỗng trên một trang hiển thị rõ ràng có nội dung có nghĩa là trang chỉ có hình ảnh. Hãy chuyển sang OCR, hoặc ít nhất là thông báo ("trang 4 không có văn bản đọc được"), thay vì để giọng đọc im lặng mà không có giải thích

Kết nối ranh giới từ SAPI với bộ theo dõi

TrackReadingWordAt, trên viewer, là bản lề của toàn bộ tính năng. Cung cấp cho nó số trang và chỉ mục ký tự; nó tìm word box chứa ký tự đó, vẽ con trỏ đọc lên đó và trả về chỉ mục từ, hoặc -1 khi chỉ mục nằm giữa các từ. Thông báo ranh giới từ của SAPI cung cấp chính xác vị trí ký tự mà nó cần:

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;

Hai chi tiết phòng thủ ở đây xứng đáng được ghi nhận. Thứ nhất, TrackReadingWordAt giữ bộ nhớ đệm word box riêng cho trang được theo dõi, được xây dựng lại tự động khi trang thay đổi, vì vậy chi phí mỗi ranh giới vẫn ổn định dù ranh giới đến nhanh đến đâu. Thứ hai, nó không kiểm tra giới hạn một cách rộng rãi. Một chỉ mục tại hoặc vượt quá số lượng ký tự của trang trả về -1 thay vì kẹp vào từ cuối cùng. Hãy xem -1 là "giữ vùng tô sáng trước đó", không bao giờ là lỗi, vì các nhóm dấu câu và khoảng trắng giữa các từ một cách hợp pháp tạo ra ranh giới không thuộc từ nào. Ghi log mỗi -1 sẽ nhấn chìm bạn. Hãy đếm chúng theo trang thay thế, và chú ý kỹ bất kỳ trang nào có tỉ lệ tăng đột biến, vì điều đó thường có nghĩa là sự không khớp chuẩn hóa văn bản từ quy tắc một

Con trỏ: màu sắc, theo dõi và dọn dẹp

SetReadingWord vẽ vùng tô sáng trực tiếp khi bạn tự giữ word box, ReadingWordColor tạo kiểu cho nó, và ReadingWordFollow := True cuộn view vừa đủ để giữ từ đang đọc trong tầm nhìn. Thuộc tính cuối đó xứng đáng với vị trí của nó. Cuộn "căn giữa từ hiện tại" tự viết làm trang giật ở mỗi lần xuống dòng, và những người đọc nhạy cảm với chuyển động sẽ tắt toàn bộ tính năng trong vòng một phút. Vùng tô sáng chỉ render trên trang hiện đang hiển thị trong TPdfView đang hoạt động, vì vậy đọc nhiều trang phải tiến PageNumber theo từng bước với giọng đọc, sau đó chạy lại bước chuẩn bị cho trang mới trước khi sự kiện ranh giới đầu tiên đến. Bỏ qua điều đó và vài vùng tô sáng đầu tiên trên mỗi trang sẽ trỏ vào tọa độ cũ

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

Tính đối xứng khi tắt là điều giữ cho vùng tô sáng trung thực. Mỗi đường dẫn tạm dừng, dừng và chuyển trang phải kết thúc bằng ClearReadingWord. Bỏ qua điều đó và một hình chữ nhật màu hổ phách nằm trên một trang đã dừng trông y như một lỗi, đây là loại thứ mỗi người kiểm tra sẽ báo cáo dù thực ra không có gì bị hỏng

Tốc độ đọc gây áp lực cho pipeline này hơn là kích thước tài liệu. Ở 300 từ mỗi phút các sự kiện ranh giới đến mỗi 200 ms, và ở tốc độ SAPI nhanh nhất chúng đến nhanh hơn mắt có thể theo dõi thoải mái. Phản ứng đúng là gộp lại, không xếp hàng. Nếu một ranh giới mới đến trong khi cập nhật vùng tô sáng vẫn đang chờ, hãy bỏ cái cũ và vẽ cái mới nhất. Một con trỏ truy cập từng từ theo thứ tự nhưng trễ nửa giây cảm thấy bị hỏng; một con trỏ đôi khi bỏ qua một từ trong khi vẫn đồng bộ với giọng đọc thì không

Các trường hợp biên phân biệt demo với sản phẩm

Một số loại tài liệu phơi bày các đường nối. Ký tự kết hợp là tinh tế nhất: các chuỗi Unicode như chữ cái cơ sở cộng với dấu kết hợp có thể chiếm nhiều chỉ mục ký tự hơn từ trực quan cho thấy, vì vậy bất kỳ phép tính số học độ lệch nào giả định một chỉ mục cho mỗi glyph sẽ dần bị lệch. Đó là lý lẽ mạnh nhất để để TrackReadingWordAt quản lý ánh xạ thay vì tự tính số từ. Dấu gạch nối thông thường hơn nhưng phổ biến hơn: một từ bị ngắt qua dòng xuống trở thành hai hộp, và nếu bạn phát âm nó như một token duy nhất, sự kiện ranh giới cho nửa sau của nó sẽ trả về hộp đầu tiên. Điều đó thường ổn, nhưng đó là một quyết định, vì vậy hãy thực hiện nó có chủ đích thay vì phát hiện ra nó. Gắn thẻ thay đổi thứ tự đọc. Khi một tài liệu có đúng thẻ cấu trúc (lãnh thổ của ISO 14289, PDF/UA), trình tự từ tuân theo cấu trúc logic; không có thẻ thì rơi về heuristic bố cục, và một trang hai cột không có thẻ có thể đọc thẳng qua cả hai cột. Trang xoay là loại phổ biến cuối cùng: mỗi Rect của từ vẫn bao nó chính xác trong không gian trang, nhưng chính sách theo dõi viewport được điều chỉnh cho luồng ngang cuộn giật khi văn bản chạy theo chiều dọc, vì vậy hãy giữ ít nhất một tài liệu được xoay trong bộ kiểm tra hồi quy. Để xử lý thứ tự đọc, đơn vị cấp câu thông qua ReadingUnits và stack hỗ trợ tiếp cận rộng hơn, hãy xem xây dựng trình đọc PDF có thể truy cập trong Delphi

Một ràng buộc nền tảng định hình việc triển khai. SAPI chỉ chạy trên Windows. API word box và theo dõi hoàn toàn giống nhau trong Lazarus và FPC, nhưng các bản dựng Linux và macOS cần một bộ tổng hợp khác được kết nối phía sau cùng các sự kiện ranh giới; thiết lập đó được trình bày trong chạy viewer trong Lazarus và FPC. Chi phí tô sáng cũng tương tác với bộ nhớ đệm trang của bạn khi tốc độ đọc tăng, và ngân sách số học trong bộ nhớ đệm render và hiệu suất thu phóng áp dụng ở đây mà không thay đổi

Khi tô sáng một từ không phải là độ chi tiết phù hợp

Karaoke cấp từ không phải lúc nào cũng là điều người đọc muốn. Ở tốc độ đọc cao, con trỏ nhấp nháy theo từng từ tự trở thành nhiễu thị giác, và một số người nghe theo dõi câu thoải mái hơn là một chuỗi nhấp nháy các từ đơn lẻ. Đối với trường hợp đó, component cung cấp một đơn vị thô hơn. ReadingUnits trả về các đơn vị cấp câu và cấp khối, mỗi đơn vị với các hình chữ nhật tô sáng riêng, và bạn vẽ chúng bằng SetReadingHighlight thay vì SetReadingWord. Cách kết nối có cùng hình dạng: một độ lệch ranh giới vẫn điều khiển đơn vị nào sáng lên, nhưng đơn vị bạn tô sáng bao gồm một mệnh đề hoặc một dòng thay vì một token đơn lẻ. Người đọc chậm hơn và phát lại tốc độ cao đều có xu hướng thích nó hơn, và không có gì ngăn bạn cung cấp cả hai chế độ sau một cài đặt

Các phiên bản sàn đáng được chú ý trước khi bạn xây dựng dựa trên điều này: word box cần PDFium Component v1.53 trở lên và con trỏ theo dõi cần v1.56. API đọc đầy đủ, các đơn vị cấp câu và bản demo đọc to đang hoạt động có trên trang sản phẩm của PDFium Component