@triabin/vue-book-reader
Version:
Forked from jinhuan138/vue-book-reader, add some features
230 lines (229 loc) • 7.03 kB
JavaScript
const h = {
XML: "http://www.w3.org/XML/1998/namespace",
SSML: "http://www.w3.org/2001/10/synthesis"
}, A = /* @__PURE__ */ new Set([
"article",
"aside",
"audio",
"blockquote",
"caption",
"details",
"dialog",
"div",
"dl",
"dt",
"dd",
"figure",
"footer",
"form",
"figcaption",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"header",
"hgroup",
"hr",
"li",
"main",
"math",
"nav",
"ol",
"p",
"pre",
"section",
"tr"
]), N = (n) => {
var e;
const t = n.lang || ((e = n == null ? void 0 : n.getAttributeNS) == null ? void 0 : e.call(n, h.XML, "lang"));
return t || (n.parentElement ? N(n.parentElement) : null);
}, E = (n) => {
var e;
const t = (e = n == null ? void 0 : n.getAttributeNS) == null ? void 0 : e.call(n, h.XML, "lang");
return t || (n.parentElement ? E(n.parentElement) : null);
}, k = (n = "en", t = "word") => {
const e = new Intl.Segmenter(n, { granularity: t }), s = t === "word";
return function* (i, r) {
var m;
const l = i.join("").replace(/\r\n/g, " ").replace(/\r/g, " ").replace(/\n/g, " ");
let g = 0, o = -1, c = 0;
const f = Array.from(e.segment(l)), u = [];
for (let a = 0; a < f.length; a++) {
const S = f[a], d = f[a + 1], b = S.segment.trim(), w = (m = d == null ? void 0 : d.segment) == null ? void 0 : m.trim(), p = /(?:^|\s)([A-Z][a-z]{1,5})\.$/.test(b), x = /^[A-Z]/.test(w || "");
if (p && x) {
const L = {
index: S.index,
segment: S.segment + ((d == null ? void 0 : d.segment) || ""),
isWordLike: !0
};
u.push(L), a++;
} else
u.push(S);
}
for (const { index: a, segment: S, isWordLike: d } of u) {
if (s && !d) continue;
for (; c <= a; ) c += i[++o].length;
const b = o, w = a - (c - i[o].length), p = a + S.length - 1;
if (p < l.length) for (; c <= p; ) c += i[++o].length;
const x = o, L = p - (c - i[o].length) + 1;
yield [
(g++).toString(),
r(b, w, x, L)
];
}
};
}, T = (n, t) => {
const e = document.implementation.createDocument(h.SSML, "speak"), { lang: s } = t;
s && e.documentElement.setAttributeNS(h.XML, "lang", s);
const i = (r, l, g) => {
if (!r) return;
if (r.nodeType === 3) return e.createTextNode(r.textContent);
if (r.nodeType === 4) return e.createCDATASection(r.textContent);
if (r.nodeType !== 1) return;
let o;
const c = r.nodeName.toLowerCase();
c === "foliate-mark" ? (o = e.createElementNS(h.SSML, "mark"), o.setAttribute("name", r.dataset.name)) : c === "br" ? o = e.createElementNS(h.SSML, "break") : (c === "em" || c === "strong") && (o = e.createElementNS(h.SSML, "emphasis"));
const f = r.lang || r.getAttributeNS(h.XML, "lang");
f && (o || (o = e.createElementNS(h.SSML, "lang")), o.setAttributeNS(h.XML, "lang", f));
const u = r.getAttributeNS(h.SSML, "alphabet") || g;
if (!o) {
const a = r.getAttributeNS(h.SSML, "ph");
a && (o = e.createElementNS(h.SSML, "phoneme"), u && o.setAttribute("alphabet", u), o.setAttribute("ph", a));
}
o || (o = l);
let m = r.firstChild;
for (; m; ) {
const a = i(m, o, u);
a && o !== a && o.append(a), m = m.nextSibling;
}
return o;
};
return i(n.firstChild, e.documentElement, t.alphabet), e;
}, y = (n, t, e) => {
const s = N(n.commonAncestorContainer), i = E(n.commonAncestorContainer), r = k(s, e), l = n.cloneContents(), g = [...t(n, r)], o = [...t(l, r)];
for (const [f, u] of o) {
const m = document.createElement("foliate-mark");
m.dataset.name = f, u.insertNode(m);
}
const c = T(l, { lang: s, alphabet: i });
return { entries: g, ssml: c };
}, M = (n) => !n.toString().trim();
function* C(n) {
let t;
const e = n.createTreeWalker(n.body, NodeFilter.SHOW_ELEMENT);
for (let s = e.nextNode(); s; s = e.nextNode()) {
const i = s.tagName.toLowerCase();
A.has(i) && (t && (t.setEndBefore(s), M(t) || (yield t)), t = n.createRange(), t.setStart(s, 0));
}
t || (t = n.createRange(), t.setStart(n.body.firstChild ?? n.body, 0)), t.setEndAfter(n.body.lastChild ?? n.body), M(t) || (yield t);
}
class I {
#t = [];
#s;
#e = -1;
#n;
constructor(t, e = (s) => s) {
this.#s = t, this.#n = e;
}
current() {
if (this.#t[this.#e]) return this.#n(this.#t[this.#e]);
}
first() {
if (this.#t[0])
return this.#e = 0, this.#n(this.#t[0]);
}
prev() {
const t = this.#e - 1;
if (this.#t[t])
return this.#e = t, this.#n(this.#t[t]);
}
next() {
const t = this.#e + 1;
if (this.#t[t])
return this.#e = t, this.#n(this.#t[t]);
for (; ; ) {
const { done: e, value: s } = this.#s.next();
if (e) break;
if (this.#t.push(s), this.#t[t])
return this.#e = t, this.#n(this.#t[t]);
}
}
find(t) {
const e = this.#t.findIndex((s) => t(s));
if (e > -1)
return this.#e = e, this.#n(this.#t[e]);
for (; ; ) {
const { done: s, value: i } = this.#s.next();
if (s) break;
if (this.#t.push(i), t(i))
return this.#e = this.#t.length - 1, this.#n(i);
}
}
}
class v {
#t;
#s;
#e;
#n = new XMLSerializer();
constructor(t, e, s, i) {
this.doc = t, this.highlight = s, this.#t = new I(C(t), (r) => {
const { entries: l, ssml: g } = y(r, e, i);
return this.#s = new Map(l), [g, r];
});
}
#r(t, e) {
return e ? t.querySelector(`mark[name="${CSS.escape(e)}"`) : null;
}
#i(t, e) {
var r, l;
if (!t) return;
if (!e) return this.#n.serializeToString(t);
const s = document.implementation.createDocument(h.SSML, "speak");
s.documentElement.replaceWith(s.importNode(t.documentElement, !0));
let i = (r = e(s)) == null ? void 0 : r.previousSibling;
for (; i; ) {
const g = i.previousSibling ?? ((l = i.parentNode) == null ? void 0 : l.previousSibling);
i.parentNode.removeChild(i), i = g;
}
return this.#n.serializeToString(s);
}
start() {
this.#e = null;
const [t] = this.#t.first() ?? [];
return t ? this.#i(t, (e) => this.#r(e, this.#e)) : this.next();
}
resume() {
const [t] = this.#t.current() ?? [];
return t ? this.#i(t, (e) => this.#r(e, this.#e)) : this.next();
}
prev(t) {
this.#e = null;
const [e, s] = this.#t.prev() ?? [];
return t && s && this.highlight(s.cloneRange()), this.#i(e);
}
next(t) {
this.#e = null;
const [e, s] = this.#t.next() ?? [];
return t && s && this.highlight(s.cloneRange()), this.#i(e);
}
from(t) {
this.#e = null;
const [e] = this.#t.find((i) => t.compareBoundaryPoints(Range.END_TO_START, i) <= 0);
let s;
for (const [i, r] of this.#s.entries())
if (t.compareBoundaryPoints(Range.START_TO_START, r) <= 0) {
s = i;
break;
}
return this.#i(e, (i) => this.#r(i, s));
}
setMark(t) {
const e = this.#s.get(t);
e && (this.#e = t, this.highlight(e.cloneRange()));
}
}
export {
v as TTS
};