Skip to content

Fix PDF text-to-speech crashes#127

Open
4rdii wants to merge 2 commits intojohnfactotum:mainfrom
4rdii:fix/pdf-tts-crashes
Open

Fix PDF text-to-speech crashes#127
4rdii wants to merge 2 commits intojohnfactotum:mainfrom
4rdii:fix/pdf-tts-crashes

Conversation

@4rdii
Copy link
Copy Markdown

@4rdii 4rdii commented Apr 11, 2026

Summary

Two small, independent fixes that together restore TTS on PDFs. Both bugs currently make PDF narration unusable:

  1. tts: fix Intl.Segmenter crash on documents without lang metadatagetLang() returns null when no language attribute is present (the normal case for PDFs). getSegmenter()'s default parameter (lang = 'en') only triggers for undefined, not null, so new Intl.Segmenter(null, ...) throws and TTS silently dies before producing a single word. Fix: lang || 'en' inside the constructor call.

  2. view: guard TTS scrollToAnchor for renderers that do not implement it — the default TTS highlight callback calls this.renderer.scrollToAnchor(range, true), but the PDF renderer doesn't implement that method (it's fixed-layout, no anchor concept). Every spoken word throws a CRITICAL log — non-fatal (audio still plays after the first fix above), but hundreds per page. Fix: wrap the default callback in a typeof guard.

Both fixes are scoped to the default PDF path and don't change behavior for reflowable formats or callers that pass their own highlight callback.

Reproduction

Before these fixes, opening any PDF (from arxiv, Google Scholar, anything) and clicking the Narration (headphones) button produces the following console output and no audio:

foliate:///foliate-js/tts.js: Unhandled Promise Rejection:
    TypeError: null is not an object (evaluating 'new Intl.Segmenter(lang, { granularity })')

With fix 1 applied, audio plays but the console is spammed:

com.github.johnfactotum.Foliate-CRITICAL: this.renderer.scrollToAnchor is not a function.

With fix 2 applied as well, PDF TTS is quiet and works end-to-end.

Test plan

  • Verified Intl.Segmenter crash reproduces on upstream main with a PDF that has no language metadata
  • Verified fix makes TTS start and produce audio
  • Verified scrollToAnchor CRITICAL spam reproduces on same PDF with fix 1 applied
  • Verified typeof guard silences the spam without affecting EPUB narration
  • Confirmed both fixes are no-ops for reflowable formats and custom highlight callbacks

🤖 Generated with Claude Code

4rdii and others added 2 commits April 12, 2026 02:16
`getLang()` walks up the DOM tree and returns `null` when no language
attribute is found. This is the common case for PDFs, which almost
never declare a language.

`getSegmenter()` has a default parameter `lang = 'en'`, but JavaScript
default parameters only apply when the argument is `undefined` — not
when it is `null`. So `getSegmenter(null)` bypasses the default and
calls `new Intl.Segmenter(null, { granularity })`, which throws:

    TypeError: null is not an object (evaluating 'new Intl.Segmenter(lang, { granularity })')

The error propagates as an unhandled promise rejection and silently
kills TTS — the user clicks Play, nothing happens, and the only
evidence is a console error. This affects every PDF regardless of
content or system configuration.

Fix: fall back to 'en' when `lang` is falsy, not just when it is
undefined. Keep the default parameter for callers that pass nothing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The default TTS highlight callback calls `this.renderer.scrollToAnchor`
to follow along with spoken text. The paginator (for reflowable books)
implements this, but the PDF renderer does not — it is fixed-layout
and has no anchor concept.

As a result, every spoken word on a PDF throws:

    this.renderer.scrollToAnchor is not a function. (In
    'this.renderer.scrollToAnchor(range, true)',
    'this.renderer.scrollToAnchor' is undefined)

These are logged at the GLib CRITICAL level — non-fatal (audio still
plays), but extremely noisy: one log per word, hundreds per page.

Fix: wrap the default callback in a typeof guard so it is a no-op for
renderers that do not implement `scrollToAnchor`. This preserves the
existing behavior for reflowable formats and eliminates the spam on
PDFs, without changing any public API. Callers that pass a custom
`highlight` callback are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant