Skip to content

EPUB: Read Aloud highlights vanish on paragraphs starting with a pagebreak anchor — CFI generator and resolver disagree on text-step numbering #244

Description

@xujialiu

Symptom

In the EPUB view, Read Aloud's highlights (the active word in word mode, and the sentence highlight) go dark for whole stretches of professionally produced EPUB3 books, while the console floods pairs of

No startContainer found for epubcfi(/6/16!/4/4/12/3,:32,:168)
Unable to get range for CFI epubcfi(/6/16!/4/4/12/3,:32,:168)

— tens per second while rendering. Playback continues normally. Reproduced on Zotero 10.0 and 10.0.1-beta.2 (their resource/reader/reader.js is byte-identical).

Root cause

The affected paragraphs start with a printed-page anchor, which mainstream EPUB3 productions emit for every printed page:

<p class="para-p"><span epub:type="pagebreak" id="page_8" role="doc-pagebreak" title="8"/>We live in the woods in Oregon. …</p>

The displayed DOM is correct — the paragraph's children are [span (empty), text] (verified live via the view's document). The generator and the resolver just disagree about how that text node is numbered:

  • The reader's position→CFI generation numbers text steps per the CFI spec — odd indices address the text between element children (EPUB CFI § Path Child References) — so the text after the leading span is step /3.
  • EpubCFI.findNode() (epubjs submodule) resolves a text step /n as the (n-1)/2-th member of the parent's text-node list: stepsToXpath() emits text()[(n-1)/2 + 1] and walkToNode() indexes this.textNodes(container)[step.index]. For /3 it looks for a second text node, which does not exist — resolution fails.

The two numbering schemes agree only while text and element children strictly alternate starting with text; any paragraph that begins with an element breaks the round trip. Verified live: interpreting the failing selector's text step per the spec (…/12/1 under epubjs's convention) resolves to exactly the expected sentence:

/6/16!/4/4/12/3,:32,:168   -> No startContainer   (as generated)
/6/16!/4/4/12/1,:32,:168   -> "Wine and hazelnut country, somewhere in that stran…"

Minimal reproduction

Any spec-numbered range CFI into <p><span/>text</p>:

// children [span, text]: the text is step /3 per the CFI spec
new EpubCFI().findNode(new EpubCFI().parse('epubcfi(/6/2!/4/2/3:4)').path.steps, doc)  // -> undefined

Suggested fix

zotero/epub.js#4 makes findNode() fall back to the spec interpretation (first text node preceded by exactly step.index element children) when the existing resolution finds nothing. The legacy interpretation still runs first, so CFIs stored with epubjs's own numbering — e.g. existing annotations — resolve exactly as before; only previously-unresolvable spec-numbered CFIs gain a result. (The alternative — teaching the reader's generator to emit epubjs-style numbering — would fix Read Aloud too, but would keep producing non-spec CFIs.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions