Thanks for your solution Abe (and sorry for disappearing, you replied just as I was going on holiday lol!)
I just tried out your suggestion and have had mixed results...
For good news, the selections/ranges code you gave works successfully! I had to tweak
Zotero.Reader.getByTabID(Zotero_Tabs.selectedID)._iframeWindow
to be
Zotero.Reader.getByTabID(Zotero_Tabs.selectedID)._internalReader._primaryView._iframe.contentWindow
instead (the first version successfully finds selected text within the UI around the article, eg selected comments on an annotation, but not within the article itself).
For mixed/bad news, the adapted selection highlighting code works perfectly, however only in the first version where it's highlighting text in the UI around the article, in the ... ._iframe.contentWindow version, docShell comes up as undefined apparently... I tested this on a PDF as well as an EPUB out of curiosity, and neither worked. Additionally, using the docShell from the working version with the updated selection finding code doesn't work (although it doesn't throw an error either interesting)
(And going back to the original "accessing the text nodes of a PDF" topic, in all this fiddling I realised I can make a TreeWalker on the document the PDF/EPUB/Snapshot is in, and use that to collect the text nodes I need)
So for both speaking a selection and speaking the whole article, I can find the nodes to be spoken and make ranges from them, but without the docShell I don't know that I can highlight them... I'm not quite sure how to proceed from here...
Thanks again in advance for any help!