feat(pdf): support TTS and annotation on PDFs, closes #2149 & #3462 (#3493)

* chore: bump jsdom to the latest version

* feat(pdf): support TTS and annotation on PDFs, closes #2149 & closes #3462
This commit is contained in:
Huang Xin
2026-03-09 17:28:19 +08:00
committed by GitHub
parent 93b96d64eb
commit 8850e6c00f
34 changed files with 841 additions and 252 deletions
@@ -143,7 +143,7 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
const range = anchor(doc);
if (!view.renderer.scrolled) {
view.renderer.scrollToAnchor(range);
view.renderer.scrollToAnchor?.(range);
} else {
const rect = range.getBoundingClientRect();
const { start, size, viewSize, sideProp } = view.renderer;
@@ -162,7 +162,7 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
const startInPrevView = offsetStart < start + headerScrollOverlap + scrollingOverlap;
if (endInNextView || startInPrevView) {
const scrollTo = offsetStart - headerScrollOverlap - scrollingOverlap;
view.renderer.scrollToAnchor(scrollTo / viewSize);
view.renderer.scrollToAnchor?.(scrollTo / viewSize);
}
}
};
@@ -319,7 +319,7 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
useEffect(() => {
const ttsHighlightOptions = viewSettings?.ttsHighlightOptions;
if (ttsControllerRef.current && ttsHighlightOptions) {
ttsControllerRef.current.initViewTTS(
ttsControllerRef.current.updateHighlightOptions(
getTTSHighlightOptions(ttsHighlightOptions, viewSettings!.isEink),
);
}
@@ -355,7 +355,7 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
// handleTTSSpeak / handleTTSStop (plain functions, registered once at mount via closure)
const handleTTSSpeak = async (event: CustomEvent) => {
const { bookKey: ttsBookKey, range, oneTime = false } = event.detail;
const { bookKey: ttsBookKey, range, index, oneTime = false } = event.detail;
if (bookKey !== ttsBookKey) return;
const view = getView(bookKey);
@@ -364,16 +364,9 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
const bookData = getBookData(bookKey);
const { location } = progress || {};
if (!view || !progress || !viewSettings || !bookData || !bookData.book) return;
if (bookData.book?.format === 'PDF') {
eventDispatcher.dispatch('toast', {
message: _('TTS not supported for PDF'),
type: 'warning',
});
return;
}
const ttsSpeakRange = range as Range | null;
let ttsFromRange = ttsSpeakRange;
let ttsFromIndex = typeof index === 'number' ? index : null;
if (!ttsFromRange && viewSettings.ttsLocation) {
const ttsCfi = viewSettings.ttsLocation;
if (isCfiInLocation(ttsCfi, location)) {
@@ -381,14 +374,16 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
const { doc } = view.renderer.getContents().find((x) => x.index === index) || {};
if (doc) {
ttsFromRange = anchor(doc);
ttsFromIndex = index;
}
}
}
if (!ttsFromRange) {
if (!ttsFromRange || !ttsFromIndex) {
ttsFromRange = progress.range;
ttsFromIndex = progress.index;
}
const currentSection = view.renderer.getContents()[0];
const currentSection = view.renderer.getContents().find((x) => x.index === ttsFromIndex);
if (ttsFromRange && currentSection) {
const ttsLocation = view.getCFI(currentSection?.index || 0, ttsFromRange);
viewSettings.ttsLocation = ttsLocation;
@@ -427,7 +422,8 @@ export const useTTSControl = ({ bookKey, onRequestHidePanel }: UseTTSControlProp
setTtsController(ttsController);
await ttsController.init();
await ttsController.initViewTTS(
await ttsController.initViewTTS(ttsFromIndex);
ttsController.updateHighlightOptions(
getTTSHighlightOptions(viewSettings.ttsHighlightOptions, viewSettings.isEink),
);
const ssml =