Fitur baca lantang memiliki satu tugas yang terlihat di luar suara: saat setiap kata diucapkan, kata tersebut harus menyala di halaman dan tetap terlihat. Untuk melakukannya Anda memerlukan kotak pembatas setiap kata, diindeks ke aliran karakter yang sama yang dibaca engine ucapan. Dapatkan kotak tetapi lewatkan pengindeksan dan sorotan akan tertinggal satu atau dua kata di belakang audio; dapatkan pengindeksan tetapi salah tangani status halaman dan sorotan mendarat di halaman yang salah sepenuhnya. Bagian ucapan ini, sintetisator itu sendiri, adalah bagian yang jarang rusak. SAPI melaporkan batas kata hingga karakter. Yang rusak adalah lapisan pemetaan tipis antara offset karakter dalam buffer ucapan dan persegi panjang di halaman yang dirender
PDFium Component menyediakan pemetaan tersebut untuk Delphi, C++Builder, dan Lazarus, dengan kotak kata tersedia sejak v1.53 dan kursor pelacak sejak v1.56. Permukaannya sengaja dibuat sempit: panggilan yang mengembalikan kotak kata untuk halaman, pelacak yang mengubah offset karakter menjadi sorotan yang diwarnai, dan beberapa properti untuk warna dan gulir otomatis. Sesempit itu, urutan Anda memanggil hal-hal menentukan apakah fitur berfungsi, dan sebagian besar kegagalan di bawah ini berasal dari memanggil fungsi yang tepat dalam urutan yang salah
Karakter bukan kata, dan engine TTS berbicara dalam karakter
Engine ucapan mengonsumsi string datar dan melaporkan kemajuan sebagai posisi karakter dalam string tersebut. Halaman PDF memiliki glyph yang ditempatkan dalam ruang halaman, di mana "kata" adalah kluster heuristik dari rangkaian glyph. Dua sistem koordinat tidak berbagi apa pun kecuali teks yang Anda berikan ke sintetisator adalah byte-demi-byte dari teks yang dihitung kotak katanya. Itu adalah aturan satu, dan tidak ada ampun. Normalkan spasi putih, hapus tanda hubung lembut, atau "bersihkan" teks yang diekstrak sebelum mengucapkannya, dan setiap offset hilir diam-diam salah. Ucapkan persis apa yang Anda ekstrak, atau simpan tabel remap offset yang eksplisit. Tidak ada opsi ketiga yang bertahan dengan dokumen nyata
Tabel remap bukan kasus tepi hipotetis. Saat UI Anda menyisipkan pengumuman halaman yang diucapkan ("halaman lima") atau memperluas singkatan untuk sintetisator, string yang diucapkan menyimpang dari yang diekstrak. Catat posisi dan panjang setiap penyisipan, lalu kurangi penyesuaian yang terakumulasi sebelum setiap panggilan pelacakan. Itu mungkin sekitar dua puluh baris pembukuan, dan itulah perbedaan antara sorotan yang bertahan pada permintaan fitur berikutnya dan sorotan yang rusak pertama kali seseorang meminta judul yang diucapkan
Apa yang diberikan kotak kata kepada Anda
Setiap catatan TPdfWordBox membawa teks kata, StartIndex dan Count karakter dalam teks halaman, Rect ruang halaman, dan nomor Page berbasis 1. Bidang StartIndex adalah jembatan antara dua sistem koordinat: itu adalah offset yang sama yang akan dikembalikan SAPI saat membaca. PageWordBoxes mengembalikan array lengkap untuk halaman aktif:
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;
Komentar tentang pengurutan sangat penting. PageWordBoxes viewer men-tokenisasi lapisan teks halaman yang saat ini ditampilkan oleh view, jadi navigasikan view terlebih dahulu dan ekstrak kedua; tidak diperlukan rendering, hanya dokumen yang terbuka. (Komponen dokumen, TPdf, mengekspos PageWordBoxes-nya sendiri yang dikunci ke Pdf.PageNumber untuk penggunaan tanpa kepala. Dua nomor halaman tersebut independen, yang merupakan jebakannya sendiri.) Hasil kosong pada halaman yang jelas membawa konten berarti pemindaian khusus gambar. Arahkan ke OCR, atau setidaknya umumkan ("halaman 4 tidak berisi teks yang dapat dibaca"), daripada membiarkan suara berhenti tanpa penjelasan
Menghubungkan batas kata SAPI ke pelacak
TrackReadingWordAt, pada viewer, adalah engsel dari seluruh fitur. Berikan nomor halaman dan indeks karakter; ia menemukan kotak kata yang berisi karakter tersebut, melukis kursor pembacaan di atasnya, dan mengembalikan indeks kata, atau -1 ketika indeks jatuh di antara kata. Notifikasi batas kata SAPI menyediakan persis posisi karakter yang diinginkannya:
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;
Dua detail defensif mendapat tempatnya di sini. Pertama, TrackReadingWordAt menyimpan cache kotak katanya sendiri untuk halaman yang dilacak, dibangun ulang secara otomatis saat halaman berubah, sehingga biaya per-batas tetap flat tidak peduli seberapa cepat batas tiba. Kedua, ia tidak memeriksa batas secara longgar. Indeks pada atau melampaui jumlah karakter halaman mengembalikan -1 daripada menjepitkan ke kata terakhir. Perlakukan -1 sebagai "pertahankan sorotan sebelumnya," jangan pernah sebagai kesalahan, karena rangkaian tanda baca dan spasi antar kata secara sah menghasilkan batas yang tidak termasuk dalam kata apa pun. Mencatat setiap -1 akan mengubur Anda. Hitung per halaman sebagai gantinya, dan perhatikan halaman mana yang rasionya melonjak, karena itu biasanya berarti ketidakcocokan normalisasi teks kembali ke aturan satu
Kursor itu sendiri: warna, ikuti, dan pembersihan
SetReadingWord melukis sorotan secara langsung saat Anda memegang kotak kata sendiri, ReadingWordColor menata gayanya, dan ReadingWordFollow := True menggulir view secukupnya untuk menjaga kata yang diucapkan tetap terlihat. Properti terakhir itu mendapat tempatnya. Gulir "pusatkan kata saat ini" buatan tangan membuat halaman bergetar pada setiap jeda baris, dan pembaca yang sensitif terhadap gerakan akan menonaktifkan seluruh fitur dalam satu menit. Sorotan hanya dirender di halaman yang saat ini ditampilkan dalam TPdfView aktif, sehingga pembacaan multi-halaman harus memajukan PageNumber seiring ucapan, lalu menjalankan ulang langkah persiapan untuk halaman baru sebelum acara batas pertamanya tiba. Lewati itu dan beberapa sorotan pertama pada setiap halaman menunjuk ke koordinat yang basi
procedure TReaderForm.StopReading;
begin
FVoice.Stop; // halt SAPI playback first
PdfView.ClearReadingWord; // then remove the highlight; a stale cursor reads as a bug
end;
Simetri saat shutdown adalah yang membuat sorotan tetap jujur. Setiap jalur jeda, berhenti, dan ganti halaman harus berakhir di ClearReadingWord. Lewati itu dan persegi panjang kuning amber duduk di halaman yang dihentikan yang terlihat persis seperti cacat, yang merupakan jenis hal yang akan diajukan setiap penguji meskipun tidak ada yang sebenarnya rusak
Kecepatan ucapan menekan pipeline ini lebih keras dari ukuran dokumen. Pada 300 kata per menit, acara batas tiba setiap 200 md, dan pada kecepatan SAPI tercepat mereka datang lebih cepat dari yang dapat dilacak mata dengan nyaman. Respons yang tepat adalah menggabungkan, bukan mengantri. Jika batas baru tiba sementara pembaruan sorotan masih menunggu, buang yang basi dan lukis yang terbaru. Kursor yang mengunjungi setiap kata secara berurutan tetapi tertinggal setengah detik terasa rusak; yang sesekali melewati kata sambil tetap tersinkronisasi dengan suara tidak terasa demikian
Kasus tepi yang memisahkan demo dari produk
Beberapa kategori dokumen mengekspos jahitannya. Karakter gabungan adalah yang paling halus: urutan Unicode seperti huruf dasar ditambah diakritik gabungan dapat menempati lebih banyak indeks karakter dari yang disarankan kata visual, sehingga aritmatika offset apa pun yang mengasumsikan satu indeks per glyph perlahan-lahan menyimpang. Itu adalah argumen terkuat untuk membiarkan TrackReadingWordAt memiliki pemetaan daripada menghitung nomor kata secara manual. Tanda hubung lebih biasa tetapi lebih umum: kata yang dipecah pada jeda baris menjadi dua kotak, dan jika Anda mengucapkannya sebagai satu token, acara batas untuk paruh keduanya mengacu ke kotak pertama. Itu biasanya tidak apa-apa, tetapi itu adalah keputusan, jadi buat dengan sengaja daripada menemukannya. Penandaan mengubah urutan pembacaan itu sendiri. Ketika dokumen membawa tag struktur yang tepat (wilayah ISO 14289, PDF/UA), urutan kata mengikuti struktur logis; tanpanya jatuh kembali ke heuristik tata letak, dan halaman dua kolom yang tidak ditandai dapat membaca langsung di kedua kolom. Halaman yang diputar adalah yang terakhir umum: setiap Rect kata masih membatasinya dengan benar dalam ruang halaman, tetapi kebijakan gulir-ikuti yang disetel untuk aliran horizontal menggulir secara jarring ketika teks berjalan secara vertikal, jadi simpan setidaknya satu dokumen yang diputar dalam set regresi. Untuk penanganan urutan membaca, unit tingkat kalimat melalui ReadingUnits, dan tumpukan asistif yang lebih luas, lihat membangun pembaca PDF yang dapat diakses di Delphi
Satu batasan platform membentuk penerapan. SAPI hanya untuk Windows. API kotak kata dan pelacakan identik byte-demi-byte di bawah Lazarus dan FPC, tetapi build Linux dan macOS memerlukan sintetisator yang berbeda yang disambungkan di belakang acara batas yang sama; pengaturan itu dibahas dalam menjalankan viewer di bawah Lazarus dan FPC. Biaya sorotan juga berinteraksi dengan cache halaman Anda setelah kecepatan ucapan naik, dan aritmatika anggaran dalam caching render dan kinerja zoom berlaku di sini tanpa perubahan
Kapan penyorotan satu kata adalah granularitas yang salah
Karaoke tingkat kata tidak selalu diinginkan pembaca. Pada kecepatan ucapan tinggi kursor yang berkedip kata demi kata menjadi kebisingan visual tersendiri, dan beberapa pendengar mengikuti kalimat dengan lebih nyaman daripada strobo kata tunggal. Untuk kasus itu komponen mengekspos unit yang lebih kasar. ReadingUnits mengembalikan unit tingkat kalimat dan blok, masing-masing dengan persegi panjang sorotannya sendiri, dan Anda melukisnya dengan SetReadingHighlight alih-alih SetReadingWord. Pengkabelannya memiliki bentuk yang sama: offset batas masih mendorong unit mana yang menyala, tetapi unit yang Anda sorot mencakup klausa atau baris daripada satu token. Pembaca yang lebih lambat dan pemutaran kecepatan tinggi keduanya cenderung lebih memilihnya, dan tidak ada yang menghalangi Anda menawarkan kedua mode di belakang pengaturan
Lantai versi layak dipasang sebelum Anda membangun terhadapnya: kotak kata membutuhkan PDFium Component v1.53 atau lebih baru, dan kursor pelacak membutuhkan v1.56. API membaca lengkap, unit tingkat kalimat, dan demo baca lantang yang berfungsi ada di halaman produk untuk PDFium Component